Java5 already contains a read-write lock in the Java.util.concurrent package. Still, we should understand the rationale behind its implementation.
- Java implementation of read/write locks (Read/write lock Java Implementation)
- Read/write lock reentrant (Read/write lock Reentrance)
- Read lock re-entry (read reentrance)
- Write lock re-enter (write reentrance)
- Read lock upgrade to write lock (read to write reentrance)
- Write lock demotion to read lock (write to read reentrance)
- Full implementation of Reentrant Readwritelock (fully reentrant Readwritelock)
- Call Unlock () in finally (calling unlock () from a finally-clause)
Java implementation of read/write locks
Let's start with an overview of the conditions for reading and writing access to resources:
Read no threads are writing, and no threads are requesting write operations.
Write no thread is doing read and write.
If a thread wants to read a resource, as long as no thread is writing to the resource and no thread is requesting a write to the resource. We assume that requests for write operations are more important than requests for read operations, and that the priority of write requests should be raised. In addition, if the read operation occurs more frequently, we do not promote the priority of the write operation, then there will be a "hunger" phenomenon. The thread that requests the write operation blocks until all read threads are unlocked from the Readwritelock. If the new thread has been guaranteed Read permission, the thread waiting for the write operation will block down, and the result is "starvation". Therefore, the read operation is guaranteed to continue only if no thread is locking the Readwritelock to write, and no thread is requesting that the lock be ready to perform a write operation.
When other threads do not read or write to a shared resource, it is possible for a thread to acquire a write lock on the shared resource, and then write to the shared resource. It is not important how many threads request write locks and in what order, unless you want to guarantee the fairness of the write lock request.
According to the above description, simple implementation of a read/write lock, the code is as follows
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) { C7/>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 ();
}
In the Readwritelock class, read locks and write locks each have a method of acquiring locks and releasing locks.
The implementation of the read lock in Lockread (), as long as no thread has a write lock (writers==0), and no thread is requesting a write lock (writerequests ==0), all threads that want to acquire a read lock are successfully fetched.
The implementation of the write lock in Lockwrite (), when a thread wants to get a write lock, first the write lock request number plus 1 (writerequests++), and then to determine whether can really get write lock, when no thread hold read lock (readers==0), And a write lock is obtained when no thread holds a write lock (writers==0). How many threads are not related to requesting write locks.
Note that the Notifyall method, not the Notify, is called in the two release lock methods (Unlockread,unlockwrite). To explain this reason, we can imagine the following scenario:
If the thread is waiting to acquire a read lock, the thread is waiting to acquire the write lock. If one of the threads waiting to read the lock is awakened by the Notify method, the awakened thread will again enter the blocking state because there is still a thread (writerequests>0) that requests the write lock. However, the thread that waits for the write lock is not awakened, as if nothing had happened (signal loss phenomenon). If the Notifyall method is used, all threads are awakened and then judged whether they can get the locks they requested.
There is another advantage in using Notifyall. If more than one read thread waits for a read lock and no thread is waiting to write a lock, all threads waiting to read the lock will be able to acquire the read lock immediately-rather than allowing only one at a time, if the Unlockwrite () is invoked.
Re-entry of read/write locks
The read/write lock (Readwritelock) implemented above is not reentrant, and is blocked when a thread that already holds a write lock requests the write lock again. The reason is that there is already a thread of writing-it is itself. In addition, consider the following example:
- Thread 1 obtained a read lock.
- Thread 2 requests a write lock, but the write lock request is blocked because thread 1 holds a read lock.
- Thread 1 again wants to request a read lock, but because thread 2 is in the state of requesting write locks, it is blocked to acquire read locks again. The above scenario uses the preceding readwritelock to be locked--a situation similar to a deadlock. No more threads can successfully acquire read locks or write locks.
To make Readwritelock reentrant, you need to make some improvements to it. The following handles the reentrant and write locks of the read locks separately.
Read lock re-entry
In order for Readwritelock read locks to be reentrant, we first have to reset the rules for read locks:
To ensure that a read lock in a thread is reentrant, either satisfies the condition that the read lock was acquired (no write or write requests), or has a read lock (regardless of whether there is a write request). To determine whether a thread already holds a read lock, you can use a map to store the threads that already hold the read lock and the number of times the corresponding thread acquires the read lock, and use the data stored in the map to determine whether a thread can acquire a read lock. Here is the modified code for method Lockread and Unlockread:
public class readwritelock{Private Map<thread, integer> readingthreads = new Hashmap<thread, integer> ();
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; }
}
In the code we can see that the read lock is allowed to be reentrant only if no thread has a write lock. In addition, read locks that are reentrant are higher than the write-lock precedence.
Write lock re-entry
Write lock entry is allowed only if a thread has already held a write lock (write lock is acquired again). The following is the modified code for method Lockwrite and Unlockwrite.
public class readwritelock{Private Map<thread, integer> readingthreads = new Hashmap<thread, integer> ();
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 how the current thread is handled when it determines whether it can acquire a write lock.
Read lock upgrade to write lock
Sometimes we want a thread with a read lock to get a write lock. To allow such an operation, this thread is required to be the only one with a read lock. Writelock () need to make some changes to achieve this goal:
public class readwritelock{Private Map<thread, integer> readingthreads = new Hashmap<thread, integer> ();
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)!= nul
L
}
}
Now the Readwritelock class can be upgraded from a read lock to a write lock.
Write lock demotion to read lock
Sometimes the thread that owns the write lock also wants to get a read lock. If a thread has a write lock, it is impossible for other threads of nature to have read locks or write locks. So for a thread that has a write lock, there is no danger in acquiring a read lock. We simply need to modify the above cangrantreadaccess method simply:
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;
}
Full implementation of Reentrant Readwritelock
Here is the complete readwritelock implementation. In order to facilitate the reading and understanding of the code, simply refactoring the above code. The code after refactoring is as follows.
public class readwritelock{Private Map<thread, integer> readingthreads = new Hashmap<thread, integer> ();
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.g
ET (callingthread)!= null;
Private Boolean Haswriter () {return writingthread!= null;
Private Boolean Iswriter (Thread callingthread) {return writingthread = = Callingthread;
Private Boolean haswriterequests () {return this.writerequests > 0;
}
}
Call Unlock () in finally
When using Readwritelock to protect critical areas, it is important to call Readunlock () and Writeunlock () in a finally block if the critical section can throw an exception. This is done to ensure that the Readwritelock can be successfully unlocked, and then other threads can request the lock. Here's an example:
Lock.lockwrite ();
try{
//do critical section code, which may throw exception
} finally {
lock.unlockwrite ();
}
The above code structure guarantees that the Readwritelock will also be released when an exception is thrown in the critical section. If the Unlockwrite method is not invoked in a finally block, when the critical section throws an exception, Readwritelock remains in the write lock state, causing all threads that invoke Lockread () or Lockwrite () to block. The only factor that can unlock the readwritelock may be that the Readwritelock is reentrant, and when an exception is thrown, the thread can then successfully fetch the lock, execute the critical section, and Invoke Unlockwrite () again, which releases Readwritelock. But what if the thread does not get the lock later? Therefore, calling Unlockwrite in finally is important for writing robust code.
The above is the Java multithreading Data collation, follow-up continue to supplement the relevant information, thank you for your support of this site!