Read/write locks in Java

Source: Internet
Author: User

I have translated an article about the Java read/write lock, because I have never read the related content of the read/write lock before. Here, even if I have learned and translated it, it may not be accurate! Well, let's talk less about it. Let's get started.

The read/write lock is more profound than the "Lock in Java" article. Imagine that you have an application that reads/writes certain resources, and the number of write operations is less than the number of read operations. Two threads that read the same resource will not cause problems. In this case, multiple threads can access resources in parallel. However, when a thread needs to write resources, other arbitrary read or write operations should not be processed at the same time. To implement write operations on only one thread at a time, multiple threads perform read operations. You need a read/write lock.

Java 5 provides read/write locks in the java. util. concurrent package. However, it is useful to understand the principles behind the read/write locks.

The topic list of this article is as follows:

1. Java implementation of read/write locks

2. Re-entry of the read/write lock

3. Re-entry of the read

4. Re-entry of write

5. Re-entry from read/write

6. Re-entry from write to read

7. complete re-entry to the ReadWriteLock class

8. Call unlock () in the finally statement ()

Java Implementation of read/write locks

First, let's summarize the conditions for read/write access to resources.

Read access: If no thread is writing access and no thread is writing access. Read-only operations do not conflict with each other)

Write Access: No threads are performing read or write access. The write-write and write-read Operations conflict)

If a thread needs to read a resource, it is okay if no thread is writing access to the resource or has requested write access. In terms of priority, we specify that the write operation request is higher than the read operation request. Otherwise, if read access is performed in most cases and write access is not prioritized, thread hunger will occur. The thread requesting write access is blocked until all read access is unlocked. If the new thread continuously performs read operations, the thread for write access will be in infinite waiting state, and the result is thread hunger. Therefore, a thread can perform read operations only when it is not locked by write access or when it is locked by requesting write access.

If no other thread is currently performing read or write access to the specified resource, the thread can perform write access to the specified resource. Unless you want to ensure the fairness of thread write access, this is irrelevant to the number of current thread requests to write access to resources.

With these simple rules, we can use the following code to implement a ReadWriteLock:

public class ReadWriteLock{  private int readers       = 0;  private int writers       = 0;  private int writeRequests = 0;  public synchronized void lockRead() throws InterruptedException{    while(writers > 0 || writeRequests > 0){      wait();    }    readers++;  }  public synchronized void unlockRead(){    readers--;    notifyAll();  }  public synchronized void lockWrite() throws InterruptedException{    writeRequests++;    while(readers > 0 || writers > 0){      wait();    }    writeRequests--;    writers++;  }  public synchronized void unlockWrite() throws InterruptedException{    writers--;    notifyAll();  }}

ReadWriteLock contains two locking methods and two unlocking methods. Both read access and write access occupy one of the locking methods and one of the unlocking methods respectively.

Read access rules are implemented by the lockRead () method. Only when no thread is requesting write access or is performing write operations, all threads can perform read access. <G id = "1"> read Operations </G>)

Write access rules are implemented by the lockWrite () method. To perform write operations on a thread, you must first perform the write access "request" action. The "request" action first checks whether the current thread can perform write operations. A thread can perform write operations only when no other thread is performing read or write operations. No matter how many threads have requested write operations, it is irrelevant.

It makes sense to use policyall () instead of policy () in both the unlockRead () and unlockWrite () methods. As to why can we imagine the following situation in the explain method:

Some threads in ReadWriteLock are waiting for read access while others are waiting for write access. Now, if a thread waiting for read access is awakened by notify (), it can only continue to wait because there are other threads requesting write access and nothing will happen. No threads get read or write access. However, by calling policyall (), All threads will be awakened to check whether they can obtain the access permissions they want.

Using policyall () has another benefit. Assume that many threads are waiting for read access and there is no thread waiting for write access. When the unlockWrite () method is called, all threads waiting for read access will be awakened at one time, rather than one by one.

Re-entry of the read/write lock

Because re-entry is not considered, the ReadWriteLock class is simpler. If a thread with write access permission requests write access again, the thread will be blocked because there is already a write operator-it itself. For more details, consider the following:

1. When a thread obtains the read permission.

2. Thread 2 requests write access permission, but it is blocked because a read operator already exists.

3. The thread repeatedly requests read access (re-entry lock), but it is also blocked because there is already a thread for writing access requests.

In this case, the previous ReadWriteLock will always be locked-similar to a deadlock. No thread can request read or write access.

To support re-entry, you need to make some changes to ReadWriteLock. The re-entry of the read operator or write operator will be processed as independent.

Re-entry of read

To enable ReadWriteLock to support re-entry by the read operator, we must first define the rules for re-entry:

If a thread can obtain the Read access permission or has obtained the Read access permission, it is reentrant.

To determine whether a thread has obtained the Read access permission, you can use the Map ing between a thread and the number of read accesses. When you want to determine whether a read access can be run, you can use the Map to determine through the reference of the corresponding call thread object. The modified lockRead () and unlockRead () methods are as follows:

public class ReadWriteLock{  private Map
 
   readingThreads =      new HashMap
  
   ();  private int writers        = 0;  private int writeRequests  = 0;  public synchronized void lockRead() throws InterruptedException{    Thread callingThread = Thread.currentThread();    while(! canGrantReadAccess(callingThread)){      wait();                                                                       }    readingThreads.put(callingThread,       (getAccessCount(callingThread) + 1));  }  public synchronized void unlockRead(){    Thread callingThread = Thread.currentThread();    int accessCount = getAccessCount(callingThread);    if(accessCount == 1){ readingThreads.remove(callingThread); }    else { readingThreads.put(callingThread, (accessCount -1)); }    notifyAll();  }  private boolean canGrantReadAccess(Thread callingThread){    if(writers > 0)            return false;    if(isReader(callingThread) return true;    if(writeRequests > 0)      return false;    return true;  }  private int getReadAccessCount(Thread callingThread){    Integer accessCount = readingThreads.get(callingThread);    if(accessCount == null) return 0;    return accessCount.intValue();  }  private boolean isReader(Thread callingThread){    return readingThreads.get(callingThread) != null;  }}
  
 

As you can see, rereading can only be allowed when no thread is currently writing resources. In addition, if the calling thread object already has the Read access permission, its read re-entry priority will be higher than any write access request.

Re-entry of write

The re-entry of write is allowed only when the calling thread object already has write access. The following code is modified after lockWrite () and unlockWrite:

public class ReadWriteLock{    private Map
 
   readingThreads =        new HashMap
  
   ();    private int writeAccesses    = 0;    private int writeRequests    = 0;    private Thread writingThread = null;  public synchronized void lockWrite() throws InterruptedException{    writeRequests++;    Thread callingThread = Thread.currentThread();    while(! canGrantWriteAccess(callingThread)){      wait();    }    writeRequests--;    writeAccesses++;    writingThread = callingThread;  }  public synchronized void unlockWrite() throws InterruptedException{    writeAccesses--;    if(writeAccesses == 0){      writingThread = null;    }    notifyAll();  }  private boolean canGrantWriteAccess(Thread callingThread){    if(hasReaders())             return false;    if(writingThread == null)    return true;    if(!isWriter(callingThread)) return false;    return true;  }  private boolean hasReaders(){    return readingThreads.size() > 0;  }  private boolean isWriter(Thread callingThread){    return writingThread == callingThread;  }}
  
 

Note that when determining whether the calling thread object can obtain write access, the write operation lock held by the thread is counted. (I really feel that this sentence is true and I will not translate it. I will post the original sentence in this post. Thank you for your understanding! Notice how the thread currently holding the write lock is now taken into account when determining if the calling thread can get write access .)

Re-entry from read/write

In some cases, a thread with the read permission must have the write permission at the same time. This situation is only possible when the thread is the only read operator. To implement this function, you need to slightly modify the writeLock () method. The Code is as follows:

public class ReadWriteLock{    private Map
 
   readingThreads =        new HashMap
  
   ();    private int writeAccesses    = 0;    private int writeRequests    = 0;    private Thread writingThread = null;  public synchronized void lockWrite() throws InterruptedException{    writeRequests++;    Thread callingThread = Thread.currentThread();    while(! canGrantWriteAccess(callingThread)){      wait();    }    writeRequests--;    writeAccesses++;    writingThread = callingThread;  }  public synchronized void unlockWrite() throws InterruptedException{    writeAccesses--;    if(writeAccesses == 0){      writingThread = null;    }    notifyAll();  }  private boolean canGrantWriteAccess(Thread callingThread){    if(isOnlyReader(callingThread))    return true;    if(hasReaders())                   return false;    if(writingThread == null)          return true;    if(!isWriter(callingThread))       return false;    return true;  }  private boolean hasReaders(){    return readingThreads.size() > 0;  }  private boolean isWriter(Thread callingThread){    return writingThread == callingThread;  }  private boolean isOnlyReader(Thread thread){      return readers == 1 && readingThreads.get(callingThread) != null;  }  }
  
 

Now the ReadWriteLock class can be re-accessed from read to write.

Re-entry from write to read

In some cases, a thread that has the write access permission must also have the Read access permission. An operator thread should always be able to obtain read access permissions. If one thread has the write access permission, other threads cannot have the read or write access permission (Note: can have the Write Request flag), so this is not a dangerous operation. The canGrantReadAccess () method after modification is as follows:

public class ReadWriteLock{    private boolean canGrantReadAccess(Thread callingThread){      if(isWriter(callingThread)) return true;      if(writingThread != null)   return false;      if(isReader(callingThread)  return true;      if(writeRequests > 0)       return false;      return true;    }}

Complete re-entry ReadWriteLock

The following is a complete re-entry ReadWriteLock implementation. I have made some minor corrections to the access conditions for reading, and it is easier for you to believe it is correct.

public class ReadWriteLock{  private Map
 
   readingThreads =       new HashMap
  
   ();   private int writeAccesses    = 0;   private int writeRequests    = 0;   private Thread writingThread = null;  public synchronized void lockRead() throws InterruptedException{    Thread callingThread = Thread.currentThread();    while(! canGrantReadAccess(callingThread)){      wait();    }    readingThreads.put(callingThread,     (getReadAccessCount(callingThread) + 1));  }  private boolean canGrantReadAccess(Thread callingThread){    if( isWriter(callingThread) ) return true;    if( hasWriter()             ) return false;    if( isReader(callingThread) ) return true;    if( hasWriteRequests()      ) return false;    return true;  }  public synchronized void unlockRead(){    Thread callingThread = Thread.currentThread();    if(!isReader(callingThread)){      throw new IllegalMonitorStateException("Calling Thread does not" +        " hold a read lock on this ReadWriteLock");    }    int accessCount = getReadAccessCount(callingThread);    if(accessCount == 1){ readingThreads.remove(callingThread); }    else { readingThreads.put(callingThread, (accessCount -1)); }    notifyAll();  }  public synchronized void lockWrite() throws InterruptedException{    writeRequests++;    Thread callingThread = Thread.currentThread();    while(! canGrantWriteAccess(callingThread)){      wait();    }    writeRequests--;    writeAccesses++;    writingThread = callingThread;  }  public synchronized void unlockWrite() throws InterruptedException{    if(!isWriter(Thread.currentThread()){      throw new IllegalMonitorStateException("Calling Thread does not" +        " hold the write lock on this ReadWriteLock");    }    writeAccesses--;    if(writeAccesses == 0){      writingThread = null;    }    notifyAll();  }  private boolean canGrantWriteAccess(Thread callingThread){    if(isOnlyReader(callingThread))    return true;    if(hasReaders())                   return false;    if(writingThread == null)          return true;    if(!isWriter(callingThread))       return false;    return true;  }  private int getReadAccessCount(Thread callingThread){    Integer accessCount = readingThreads.get(callingThread);    if(accessCount == null) return 0;    return accessCount.intValue();  }  private boolean hasReaders(){    return readingThreads.size() > 0;  }  private boolean isReader(Thread callingThread){    return readingThreads.get(callingThread) != null;  }  private boolean isOnlyReader(Thread callingThread){    return readingThreads.size() == 1 &&           readingThreads.get(callingThread) != null;  }  private boolean hasWriter(){    return writingThread != null;  }  private boolean isWriter(Thread callingThread){    return writingThread == callingThread;  }  private boolean hasWriteRequests(){      return this.writeRequests > 0;  }}
  
 

Call the unlock () method in the finally statement

When ReadWriteLock is used to protect a key code segment and an exception may be thrown, it is very important to call the readUnlock () and writeUnlock () methods in the finally statement. This is done to ensure that the corresponding ReadWriteLock is unlocked and other threads can lock it. The following is an example:

lock.lockWrite();try{  //do critical section code, which may throw exception} finally {  lock.unlockWrite();}

The small code structure above ensures that the corresponding ReadWriteLock object can be unlocked when the key code throws an exception. If the finally statement does not call unlockWrite () and an exception is thrown from the key code, the ReadWriteLock object will always remain in the locked state of the write operation. This causes other threads that call the lockRead () or lockWrite () method of the ReadWriteLock object to block. It is possible to unlock the ReadWriteLock only when it is re-entered. At the same time, the thread that throws an exception and locks it will lock it later, and then execute the key code and unlock it. This is a possibility of unlocking, but why wait for this situation to happen? Calling the unlockWrite () method directly in finally statements is a more robust solution.

OK. To tell the truth, I have been confused. Fortunately, it is not difficult, but some terms are quite vague. Think about writing a test case another day, which should be easier to understand.

Mount Please retain Source: http://blog.csdn.net/u011638883/article/details/18605761

Thank you !!

Address: http://tutorials.jenkov.com/java-concurrency/read-write-locks.html

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.