I. Introduction to read and write locks
In reality there is a scenario where read and write operations are done on shared resources, and write operations are not as frequent as read operations. There is no problem with multiple threads reading a resource at the same time without a write operation, so multiple threads should be allowed to read shared resources at the same time, but if a thread wants to write these shared resources, it should not allow other threads to read and write to the resource.
For this scenario,Java's concurrency package provides read-write lock Reentrantreadwritelock, which represents two locks, one is read-related locks, called shared locks, and a write-related lock, called an exclusive lock, is described as follows:
Prerequisites for the thread to enter the read lock:
No other thread has a write lock,
There are no write requests or write requests, but the calling thread and the thread holding the lock are the same
Prerequisites for a thread to enter a write lock:
No read lock for other threads
No other thread's write lock
The read-write lock has the following three important features:
Second, the source code interpretation 1, internal class
There are many inner classes in the read-write lock implementation class, so let's look at the definitions of these classes:
Public class Implements Readwritelock, java.io.Serializable
The read-write lock does not implement the lock interface, but it implements the Readwritelock. There are not many real implementations of the lock interface in the concurrency series, except for the previously mentioned re-entry lock (Reentrantlock), and the two internal classes in the read-write lock to implement read and write locks:
Public Static class Implements Lock, java.io.Serializable
Public Static class Implements Lock, java.io.Serializable
In addition, the read-write lock is also designed as a template method mode, which provides the characteristics of fair and non-fair locking by inheriting the queue synchronizer:
Static Abstract class extends Abstractqueuedsynchronizer
Final Static class extends Sync
Final Static class extends Sync
2. Design of reading and writing state
The synchronization state in the previous implementation of the re-entry lock is the number of times that the same thread is repeatedly fetched, that is, an shaping variable to maintain, but the previous representation simply indicates whether it is locked, without having to distinguish between a read lock or a write lock. A read-write lock requires the state of multiple read threads and a write thread to be maintained on a synchronous state (an shaping variable).
Read-write lock the implementation of the synchronization state is the use of a bitwise cut on an integer variable: The variable is cut into two parts, the high 16 bits represent the read, and the lower 16 bits represent the write.
Assume that the current synchronization state value is S,get and set as follows:
1. Get Write Status:
S&0X0000FFFF: Erase all high 16 bits
2. Get Read status:
S>>>16: Unsigned 0, 16-bit right shift
3, write State plus 1:
S+1
4. Read State plus 1:
S+ (1<<16) is S + 0x00010000
In the code layer's judgment, if S is not equal to 0, when the Write state (S&0X0000FFFF), and the Read state (S>>>16) is greater than 0, the read lock of the read-write lock is obtained.
3. Write lock acquisition and release
protected Final BooleanTryacquire (intacquires) {Thread current=Thread.CurrentThread (); intc =getState (); intW =Exclusivecount (c); if(c! = 0) { //(note:if C! = 0 and W = = 0 Then shared count! = 0) if(w = = 0 | | current! =Getexclusiveownerthread ())return false; if(W + exclusivecount (acquires) >max_count)Throw NewError ("Maximum Lock count Exceeded"); } if(w = = 0 && writershouldblock (current)) | | !compareandsetstate (c, C +acquires)) return false; Setexclusiveownerthread (current); return true; }
1, C is to get the current lock state; W is the state that gets the write lock.
2, if the lock state is not zero, and the state of the write lock is 0, then the read lock state is not 0, so when the front-thread cannot get the write lock. Or the lock state is not zero, and the state of the write lock is not 0, but the thread that gets the write lock is not the current thread, and the current thread cannot acquire a write lock.
A write lock is a reentrant lock that increases the presence of a read lock when the synchronization state is acquired.
The release of the write lock is similar to the Reentrantlock release process, where each release writes the write state minus 1 until the write state is 0 o'clock, indicating that the write lock is released.
4. Acquisition and release of Read lock
1 protected Final intTryacquireshared (intunused) {2Thread current =Thread.CurrentThread ();3 intc =getState ();4 if(Exclusivecount (c)! = 0 &&5Getexclusiveownerthread ()! =Current )6 return-1;7 if(Sharedcount (c) = =max_count)8 Throw NewError ("Maximum Lock count Exceeded");9 if(!readershouldblock (current) &&TenCompareandsetstate (c, C +shared_unit)) { OneHoldcounter RH =Cachedholdcounter; A if(RH = =NULL|| Rh.tid! =Current.getid ()) -Cachedholdcounter = RH =readholds.get (); -rh.count++; the return1; - } - returnfulltryacquireshared (current); -}
1. A read lock is a shared lock that supports re-entry and can be acquired simultaneously by multiple threads.
2, in the absence of a write state of 0 o'clock, the read lock is always successfully obtained, and the only thing to do is to increase the read state (thread safety)
3. The read state is the sum of the number of read locks taken by all threads, and the number of times each thread acquires a read lock can only be saved in threadlocal and maintained by the thread itself.
Each release of a read lock reduces the state (thread-safe, there may be multiple read threads releasing the lock at the same time), and the reduced value is 1<<16.
5. Lock downgrade
Lock demotion refers to a write lock demotion to a read lock: Holding the currently owned write lock, acquiring a read lock, and then releasing the previously owned write lock process.
The lock escalation is to turn the read lock into a write lock, but Reentrantreadwritelock does not support this approach.
Let's look at the lock escalation program first:
1 New Reentrantreadwritelock (); 2 Rwl.readlock (). Lock (); 3 System.out.println ("Get Readlock"); 4 Rwl.writelock (). Lock (); 5 System.out.println ("Get Writelock");
This line gets read lock, does not release immediately after acquiring write lock, will lead to deadlock!!!
1 New Reentrantreadwritelock (); 2 Rwl.writelock (). Lock (); 3 System.out.println ("Get Writelock"); 4 Rwl.readlock (). Lock (); 5 System.out.println ("Get Readlock");
This process is exactly the opposite of the above, and the program can run normally without a deadlock. However, lock demotion does not automatically release write locks. The release still needs to be displayed.
Since the read-write lock is used for reading and writing less scenes, the natural use to implement the cache, the following is a simple implementation of the cache demo:
1 ImportJava.util.HashMap;2 ImportJava.util.concurrent.locks.ReadWriteLock;3 ImportJava.util.concurrent.locks.ReentrantReadWriteLock;4 5 6 Public classcachedtest7 {8 volatileHashmap<string,string> Cachemap =NewHashmap<string,string>();9 TenReadwritelock RwLock =NewReentrantreadwritelock (); One A Publicstring GetS (string key) - { - Rwlock.readlock (). Lock (); theString value =NULL; - Try - { - if(Cachemap.get (key) = =NULL) + { - rwlock.readlock (). Unlock (); + Rwlock.writelock (). Lock (); A Try at { - //you need to judge again to prevent the later blocked thread from putting the data again - if(Cachemap.get (key) = =NULL) - { -Value = "" +Thread.CurrentThread (). GetName (); - cachemap.put (key, value); inSystem.out.println (Thread.CurrentThread (). GetName () + "put the value" +value); - } to } + finally - { the //here is the lock demotion, the acquisition of the read lock and the release order of the write lock cannot be reversed * Rwlock.readlock (). Lock (); $ rwlock.writelock (). Unlock ();Panax Notoginseng } - } the } + finally A { the rwlock.readlock (). Unlock (); + } - returnCachemap.get (key); $ } $}
1, the business logic is very good understanding, a thread in the first to obtain a read lock, if there is no value in the map, then release the read lock, get write lock, the value of the thread into the map.
2, there are two times the value is empty judgment, the first judgment is very good understanding, the second judgment is to prevent the current thread in the acquisition of write locks, the other threads blocked in the acquisition of write lock place. When the vaule is placed in the map, the write lock is released. If there is no value in this position, subsequent threads that acquire a write lock assume that the map is still empty, and the value is placed in the map again, overwriting the previous value value, which is obviously not what we would like to see.
3, in the position of line 35th, here handle the logic of the lock demotion. According to our normal logical thinking, because it is to release the write lock, and then get read lock. So why do lock downgrades work? The answer is to ensure the visibility of the data, because if the current thread does not acquire a read lock but instead directly releases the write lock, if the thread is releasing the write lock and acquiring a read lock for the time period, there is another thread that acquires the write lock and modifies the data, then the front thread cannot perceive the change of the data. If you follow the principle of lock demotion, then when the front thread acquires a read lock, it blocks other threads from acquiring a write lock, and the data is not altered by other threads, thus guaranteeing the consistency of the data.
Reentrantreadwritelock Read and Write lock