当前位置:网站首页>Come on in! Take a few minutes to see how reentrantreadwritelock works!

Come on in! Take a few minutes to see how reentrantreadwritelock works!

2020-11-07 16:46:57 itread01

> ** Preface ** > > > After reading ReentrantLock After that , In high concurrency scenarios ReentrantLock It's enough to use , But because ReentrantLock It's an exclusive lock , At the same time, only one thread can acquire the lock , And many application scenarios are read more and write less , Use... At this time ReentrantLock It's not very appropriate . How to use a scene with more than write and read ? stay JUC The package also provides read and write locks ReentrantReadWriteLock To deal with reading, writing and writing less scenes . > > > Public number :『 Liu Zhihang 』, Record the skills in work study 、 Development and source code Notes ; From time to time to share some of the life experience . You are welcome to guide ! ### Introduce Support is similar to ReentrantLock Semantic ReadWriteLock The realization of . It has the following properties : - ** Acquisition order ** This class does not impose read or write precedence on the sort of lock access . however , It does support optional * fair * Strategy . Support ** Fair model ** and ** The unfair model **, The default is ** The unfair model **. - ** Reentrant ** Allow reader and writer according to `ReentrantLock` The style of the gets the read lock or the write lock again . After the write thread releases all the write locks it holds ,reader To allow reentry to use them . Besides ,writer You can get a read lock , But the opposite is not true . - ** Lock downgrade ** Reentry also allows demotion from a write lock to a read lock , By getting the write lock first , Then get the read lock , Finally, release the write lock in a way that demotes . however , Upgrading from read lock to write lock is ** impossible **. - ** Interrupt of lock acquisition ** Both read and write locks support interrupts during lock acquisition . - **`Condition` Support ** The write lock provides a `Condition` Realize , For write locks , The implementation is similar to `ReentrantLock.newCondition()` Provided `Condition` To achieve right `ReentrantLock` Do the same thing . Of course , this `Condition` Can only be used for writing locks . Read lock not supported `Condition`. - ** Monitoring ** This class supports methods for determining whether to hold or contest locks . These methods are designed to monitor system status , Instead of synchronous control . Locks support at most 65535 A recursive lock and 65535 A read lock The above is *Java Api official document * [1] The explanation of , To sum up, the contents are as follows : 1. Support unfair and fair models , The default is unfair mode . 2. Support reentry , Read locks can be re entered to acquire read locks , Write lock can be re entered to get write lock , Write lock can acquire read lock , Read lock cannot acquire write lock . 3. Locks can be degraded , Demote from write lock to read lock , But it's impossible to upgrade from read lock to write lock . #### Basic use ```java class CachedData { Object data; volatile boolean cacheValid; final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); void processCachedData() { // Read lock and lock rwl.readLock().lock(); if (!cacheValid) { // Read lock must be released before getting write lock rwl.readLock().unlock(); // Write lock and lock rwl.writeLock().lock(); try { // Re check status , Because another thread might // The write lock was obtained and the state was changed before the operation was performed if (!cacheValid) { data = ... cacheValid = true; } // Demote by acquiring a read lock before releasing the write lock rwl.readLock().lock(); } finally { rwl.writeLock().unlock(); // Unlock write, still hold read } } try { use(data); } finally { rwl.readLock().unlock(); } } } ``` It's just one of the official documents demo. #### Question question 1. stay ReentrantReadWriteLock in state What does it mean ? 2. What is the process of thread acquiring lock ? 3. How to realize the reentrancy of read lock and write lock ? 4. Current thread failed to get lock , What is the subsequent operation blocked ? 5. How is lock demotion degraded ? ### Source code analysis #### Code structure
```java public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable { private static final long serialVersionUID = -6992448646407690164L; /** Internal classes that provide read locks */ private final ReentrantReadWriteLock.ReadLock readerLock; /** Internal classes that provide write locks */ private final ReentrantReadWriteLock.WriteLock writerLock; /** Perform all synchronization mechanisms */ final Sync sync; } ``` #### state I was reading before ReentrantLock At the time of the original code state Represents the state of the lock ,0 Indicates that no thread holds a lock , Bigger than 1 Indicates the number of times a thread has held the lock and re entered . And in the ReentrantReadWriteLock It's a read-write lock , Then you need to store ** Read lock ** and ** Write lock ** Two states of , How does that mean ? stay ReentrantReadWriteLock There is also a Sync Inherited AbstractQueuedSynchronizer, It's also FairSync、NonfairSync The parent class of . Internally defined state Some operations of . ```java abstract static class Sync extends AbstractQueuedSynchronizer { private static final long serialVersionUID = 6317671515068378041L; // Shift number static final int SHARED_SHIFT = 16; // Unit static final int SHARED_UNIT = (1 << SHARED_SHIFT); // The maximum number 1 << 16 -> 65536 static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1; // Calculate exclusive numbers using 1 << 16 -> 65536 static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1; // Returns the number of shared reservations static int sharedCount(int c) { return c >>> SHARED_SHIFT; } // Returns the exclusive reserve number static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; } } ``` stay AQS Defined in state For int Type , And in the ReentrantReadWriteLock in , Will state Of high 16 Bit and low 16 Bit splitting means read-write lock . Among them high 16 Bits indicate read lock , low 16 Bit indicates write lock . Use... Separately sharedCount and exclusiveCount Method to get the current state of the read lock and the write lock .
From the perspective of read lock and write lock, we can see how to lock and release lock ? #### ReadLock.lock ```java public static class ReadLock implements Lock, java.io.Serializable { /** * Get read lock . * If the write lock is not held by another thread , Then get the read lock and return immediately . * If the write lock is held by another thread , For thread scheduling purposes , * The current thread will be disabled , And in a dormant state , Until you get the read lock . */ public void lock() { // call AQS Access to shared resources sync.acquireShared(1); } } ``` ![ReentrantReadWriteLock-AQS-Share-gTrD2e](https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/ReentrantReadWriteLock-AQS-Share-gTrD2e.png) Access to shared resources , This one uses AQS The logic of , among tryAcquireShared(arg) Is in ReentrantReadWriteLock.Sync Implemented in . And AQS There's a rule in ,tryAcquireShared There are three return values : 1. Smaller than 0: It means failure ; 2. Equal to 0: Indicates that the shared mode has successfully obtained resources , However, subsequent nodes cannot succeed in shared mode ; 3. Bigger than 0: Indicates that the shared mode has successfully obtained resources , Subsequent nodes may also succeed in sharing mode , In this case , Subsequent wait threads must check for availability . ```java abstract static class Sync extends AbstractQueuedSynchronizer { protected final int tryAcquireShared(int unused) { Thread current = Thread.currentThread(); // Get state value int c = getState(); // Exclusive count is not 0 And Not the current thread , Indicates that there is a write lock if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current) return -1; // Get share count ( Read lock count ) int r = sharedCount(c); // There is no need to block the read lock && The share count is less than the maximum && state The update is successful if (!readerShouldBlock() && r < MAX_COUNT && compareAndSetState(c, c + SHARED_UNIT)) { if (r == 0) { // The current read lock count is 0 // firstReader Is the first thread to get a read lock // firstReaderHoldCount yes firstReader Keep count of firstReader = current; firstReaderHoldCount = 1; } else if (firstReader == current) { // Read lock reentry firstReaderHoldCount++; } else { // Current cache count HoldCounter rh = cachedHoldCounter; // The current thread does not count perhaps There is no counter set up if (rh == null || rh.tid != getThreadId(current)) // Set up the count , Based on ThreadLocal cachedHoldCounter = rh = readHolds.get(); else if (rh.count == 0) readHolds.set(rh); // Count up rh.count++; } return 1; } // Get the shared lock method completely , As tryAcquireShared The method is due to CAS Handling after failed lock acquisition . // Because the front may fail CAS Failure , Queuing policy failure and other reasons . return fullTryAcquireShared(current); } } ``` 1. Get... First state , Through exclusiveCount Method to get the count value of write lock , Not for 0 And Not the current thread , Indicates that there is a write lock . return -1 Failure . 2. Through sharedCount Get read lock count , After judging whether blocking is needed and whether the upper limit is exceeded , Use CAS to update Read lock count . 3. Set or update firstReader、firstReaderHoldCount、 cachedHoldCounter. 4. Finally, we will complete the shared lock acquisition method , As the subsequent processing method of previous acquisition failure . firstReader:firstReader Is the first thread to get a read lock ; firstReaderHoldCount:firstReaderHoldCount yes firstReader Keep count of . That is, the number of reentries of the first thread that acquired the read lock . cachedHoldCounter: The number of reentries that the last thread that acquired the read lock obtained the read lock . ```java final int fullTryAcquireShared(Thread current) { HoldCounter rh = null; // Infinite loops for (;;) { int c = getState(); // Whether there is a write lock if (exclusiveCount(c) != 0) { // There's a write lock , But not the current thread , Direct return failed if (getExclusiveOwnerThread() != current) return -1; } else if (readerShouldBlock()) { // Need to block // No write lock , Make sure the read lock is not regained if (firstReader == current) { // assert firstReaderHoldCount > 0; } else { // Read lock count for current thread ThreadLocal in if (rh == null) { rh = cachedHoldCounter; if (rh == null || rh.tid != getThreadId(current)) { rh = readHolds.get(); // End of count ,remove fall if (rh.count == 0) readHolds.remove(); } } // For 0 Direct failure if (rh.count == 0) return -1; } } // Reaching the limit Throw exception if (sharedCount(c) == MAX_COUNT) throw new Error("Maximum lock count exceeded"); // CAS Set the read lock if (compareAndSetState(c, c + SHARED_UNIT)) { if (sharedCount(c) == 0) { firstReader = current; firstReaderHoldCount = 1; } else if (firstReader == current) { firstReaderHoldCount++; } else { if (rh == null) rh = cachedHoldCounter; if (rh == null || rh.tid != getThreadId(current)) rh = readHolds.get(); else if (rh.count == 0) readHolds.set(rh); rh.count++; cachedHoldCounter = rh; // cache for release } return 1; } } } ``` 1. First of all, it's going to go back and forth 2. There's a write lock , But not the current thread , Direct return failed .** however , There's a write lock , If it's the current thread , It's going to continue .** 3. Set or update firstReader、firstReaderHoldCount、 cachedHoldCounter. When there is a write lock ( Exclusive lock ) When , Method will return -1 Failure , We'll call later AQS Of doAcquireShared Method , Circle around to get resources .doAcquireShared The method will go back and forth , Try to get a read lock , Once you get the read lock , The current node will immediately wake up the subsequent nodes , Subsequent nodes begin to try to get read locks , Spread in turn . ![ReentrantReadWriteLock-1-rl0DjC](https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/ReentrantReadWriteLock-1-rl0DjC.png) #### ReadLock.unlock ```java public static class ReadLock implements Lock, java.io.Serializable { public void unlock() { sync.releaseShared(1); } } ``` call AQS Of releaseShared Release shared resources method . ![ReadLock-unlock-LE7vUH](https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/ReadLock-unlock-LE7vUH.png) among tryReleaseShared Yes ReadLock Realize . ```java protected final boolean tryReleaseShared(int unused) { Thread current = Thread.currentThread(); if (firstReader == current) { // The first thread is the current thread if (firstReaderHoldCount == 1) firstReader = null; else firstReaderHoldCount--; } else { // The first thread is not the current thread , Update your own ThreadLocal The count inside HoldCounter rh = cachedHoldCounter; if (rh == null || rh.tid != getThreadId(current)) rh = readHolds.get(); int count = rh.count; if (count <= 1) { readHolds.remove(); if (count <= 0) throw unmatchedUnlockException(); } --rh.count; } // Turn around for (;;) { int c = getState(); int nextc = c - SHARED_UNIT; // Use CAS to update state if (compareAndSetState(c, nextc)) // But if the read and write locks are now released , // It may allow waiting writers to continue . return nextc == 0; } } ``` 1. If it's the first thread , Direct update technology , If not, update yourself ThreadLocal The count stored in it . 2. Turn around , Use CAS to update state Value . 3. If state The updated value is 0, Indicates that no thread holds a read lock or a write lock . 4. When state For 0, This will call AQS Of doReleaseShared Method . At this time, if the queue has a write lock , The lock will be taken by the write lock . #### WriteLock.lock ```java public static class WriteLock implements Lock, java.io.Serializable { /** * Get write lock . * If no other thread holds a read lock or a write lock , Will go straight back to , And set the write lock count to 1. * If the current thread holds a write lock , Then write locks are counted +1, And back . * If the lock is being held by another thread , The current thread is used for thread scheduling purposes , * The current thread will be disabled , And in a dormant state , Until the read lock is obtained and the write lock count is set to 1. */ public void lock() { sync.acquire(1); } } ``` ![WriteLock.lock-wBuvUA](https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/WriteLock.lock-wBuvUA.png) tryAcquire Method by Write Realize by yourself , The way and ReentrantLock Similar . ```java protected final boolean tryAcquire(int acquires) { // If the read lock count is nonzero or the write lock count is nonzero , And the owner is another thread , Then failure . // If the count is saturated , Then failure . Only in count When it's not zero , That would have happened . // Otherwise , If the thread is allowed by the reentrant fetch or queuing policy , Is eligible to lock in . // If so , Please update the status and set the owner . Thread current = Thread.currentThread(); int c = getState(); // Write lock count int w = exclusiveCount(c); // c != 0 It indicates that there is thread to get the lock if (c != 0) { // (Note: if c != 0 and w == 0 then shared count != 0) // Judge whether you are , Not myself return false if (w == 0 || current != getExclusiveOwnerThread()) return false; // Judge if the upper limit has been exceeded if (w + exclusiveCount(acquires) > MAX_COUNT) throw new Error("Maximum lock count exceeded"); // Reentrant setState(c + acquires); return true; } // There is no need to block , perhaps CAS to update state Failure if (writerShouldBlock() || !compareAndSetState(c, c + acquires)) return false; setExclusiveOwnerThread(current); return true; } ``` 1. Get state , If state Not for 0 Whether the current thread is reentrant fetched . 2. state For 0 , Then the current thread CAS to update state, Acquire lock . 3. Bind the current thread after the update is successful . 4. If it fails, the call will continue AQS Of acquireQueued, Put the current block on AQS In the queue .AQS It's going to go back and forth , Wait for the last lock to release , Try to get the lock . ![ReentrantReadWriteLock-2-mQAgGL](https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/ReentrantReadWriteLock-2-mQAgGL.png) #### WriteLock.unlock ```java public static class WriteLock implements Lock, java.io.Serializable { // If the current thread is the holder of this lock , Keep the count down . // If you keep the current count to zero , Then unlock . // If the current thread is not the holder of the lock IllegalMonitorStateException Abnormal . public void unlock() { sync.release(1); } } ``` ![Write-unlock-bwHAcw](https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/Write-unlock-bwHAcw.png) The same code is used AQS The logic of ,tryRelease Partly from WriteLock Realize by yourself . ```java protected final boolean tryRelease(int releases) { if (!isHeldExclusively()) throw new IllegalMonitorStateException(); int nextc = getState() - releases; boolean free = exclusiveCount(nextc) == 0; if (free) setExclusiveOwnerThread(null); setState(nextc); return free; } ``` 1. If it is the current thread to be reentry , The number of times of weight reduction . 2. After deduction, if it is 0, Set the lock hold thread to null, to update state value .AQS The subsequent nodes are waken up to get the lock . ### Summary #### The problem is **Q:** stay ReentrantReadWriteLock in state What does it mean ? **A:**state Represents the state of the lock .state For 0 , Thread holds no lock ,state The height of 16 Represents the read lock state , low 16 Represents the write lock state . The actual value of the read-write lock can be obtained by bit operation . **Q:** What is the process of thread acquiring lock ? **A:** Please refer to the original code notes above , And the flow chart that follows . **Q:** How to realize the reentrancy of read lock and write lock ? **A:** When locking , Determine if it is the current thread , If it's the current thread , Then the count is accumulated directly . It is worth noting that : Read lock reentry count uses ThreadLocal On line cache count , The write lock is used directly state Add up ( Actually with state low 16 Bits are added together ). **Q:** Current thread failed to get lock , What is the subsequent operation blocked ? **A:** Get failed , It's going to put AQS Waiting in the queue , Circle around in the queue , Monitor whether the previous node is head , Yes , Will try to acquire the lock again . **Q:** How is lock demotion degraded ? **A:** ![write-to-read-koAuqm](https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/write-to-read-koAuqm.png) As shown in the picture , In the circle section fullTryAcquireShared In the code , You can see it , When you get a read lock , If the current thread holds a write lock , It is possible to obtain a read lock . This refers to the demotion of the lock , For example, thread A Got a write lock , When the thread A At the end of the execution , It needs access to current information , Suppose lock demotion is not supported , Will lead to A Release the write lock , And then request the read lock again . In the middle of this, it is possible for other blocked threads to acquire the write lock . This leads to threads A The data is inconsistent during one execution . ### Summary 1. ReentrantReadWriteLock Read write lock , The internal implementation is ReadLock Read lock and WriteLock Write lock . Read lock , Allow sharing ; Write lock , It's an exclusive lock . 2. Both read and write locks support reentry , The number of re-entry of read lock records the number of thread maintenance ThreadLocal in , Write lock maintenance in state On ( low 16 position ). 3. Support lock demotion , Demote from write lock to read lock , Prevent dirty reading . 4. ReadLock and WriteLock All through AQS To achieve . If the lock fails to be obtained, it will be put into AQS Waiting in the queue , And then I kept trying to get the lock . The difference is that the read lock is put in the wait queue only when there is a write lock , As long as the thread lock exists, not as long as the thread lock exists ( Whether it's a write lock or a read lock ) Will be put in the queue .!![read-write-different-oI9wB1](https://cdn.jsdelivr.net/gh/liuzhihang/oss/pic/article/read-write-different-oI9wB1.png) 5. Through source code analysis , It can be concluded that the read and write lock is suitable for ** Read more and write less ** In the scene of . #### Relevant information [1] Java Api:https://docs.oracle.com/javase/8/docs/api/overview-summ

版权声明
本文为[itread01]所创,转载请带上原文链接,感谢