ReentrantReadWriteLock is an exclusive Lock that allows access by only one thread at a time. ReentrantReadWriteLock allows access by multiple reader threads at the same time. However, the writer thread and the reader thread, and the writer thread and the writer thread are not allowed to access simultaneously. Improved concurrency over exclusive locking. In practice, access to shared data (such as caches) is mostly read rather than write, so ReentrantReadWriteLock provides better concurrency and throughput than exclusive locks.

Read/write locks maintain two internal locks, one for read operations and one for write operations. All ReadWriteLock implementations must ensure that the memory synchronization effect of writeLock operations also remains associated with the associated readLock. That is, the thread that successfully acquired the read lock will see all updates made to the version before the lock was written.

ReentrantReadWriteLock supports the following functions:

1) Support fair and unfair lock acquisition methods;

2) Reentrant support. The read thread can also acquire the read lock after acquiring the read lock. After acquiring the write lock, the writer thread can acquire both the write lock and the read lock again.

3) It is also allowed to degrade from write lock to read lock by acquiring write lock, then acquiring read lock, and finally releasing write lock. However, upgrading from a read lock to a write lock is not allowed;

4) Both read and write locks support interruption during lock acquisition;

5) Condition support. Write-only locks provide a Conditon implementation; Read the lock does not support Conditon, readLock (.) newCondition will throw an UnsupportedOperationException ().

Use example 1: Use reentrant to perform lock degradation after upgrade cache

1 class CachedData { 2 Object data; 3 volatile boolean cacheValid; 4 ReentrantReadWriteLock RWL = new ReentrantReadWriteLock(); 5 6 void processCachedData() { 7 rwl.readLock().lock(); // If cache is invalid, update cache; Otherwise use data 9 if (! CacheValid) {10 // Must release read lock before acquiring write lock 11 // readLock().unlock(); 13 rwl.writeLock().lock(); 14 // Recheck state because another thread might have acquired 15 // write lock and changed state before we did. 16 if (! cacheValid) { 17 data = ... 18 cacheValid = true; Rwl.readlock ().lock(); rwl.readlock ().lock(); rwl.readlock ().lock(); 23 rwl.writeLock().unlock(); // Unlock write, still hold read 24 } 25 26 use(data); 27 rwl.readLock().unlock(); // Release read lock 28} 29}Copy the code

Example 2: Use ReentrantReadWriteLock to improve Collection concurrency

This is usually attempted when the collection data is large, the reader thread accesses it more than the writer thread, and the cost of the operation in question is higher than the synchronization cost.

1 class RWDictionary { 2 private final Map<String, Data> m = new TreeMap<String, Data>(); 3 private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); 4 private final Lock r = rwl.readLock(); Private final Lock w = rwl.writelock (); Public Data get(String key) {8 r.lock(); 9 try { return m.get(key); } 10 finally { r.unlock(); } 11 } 12 public String[] allKeys() { 13 r.lock(); 14 try { return m.keySet().toArray(); } 15 finally { r.unlock(); } 16 } 17 public Data put(String key, Data value) { 18 w.lock(); 19 try { return m.put(key, value); } 20 finally { w.unlock(); } 21 } 22 public void clear() { 23 w.lock(); 24 try { m.clear(); } 25 finally { w.unlock(); } 26} 27}Copy the code

Implementation Principle ReentrantReadWriteLock is also based on AQS. Its custom synchronizer (inheriting AQS) needs to maintain the state of multiple reader threads and one writer thread on the synchronization state (an integer variable state), making the design of the state a key to the implementation of read and write locks. If you want to maintain multiple states on an integer variable, you must “bitwise slice” the variable. Read/write locks split the variable into two parts, with 16 bits higher for read and 16 bits lower for write.

The domain ReentrantReadWriteLock contains two locks, readerLock and writerLock. ReadLock and WriteLock are internal classes.

1 /** Inner class providing readlock */
2 private final ReentrantReadWriteLock.ReadLock readerLock;
3 /** Inner class providing writelock */
4 private final ReentrantReadWriteLock.WriteLock writerLock;
5 /** Performs all synchronization mechanics */
6 final Sync sync;
Copy the code

WriteLock a WriteLock is a reentrant exclusive lock that uses the AQS exclusive strategy for obtaining synchronization state.

(1) Get write lock

1 public void lock() {3 sync.acquire(1); Public final void acquire(int arg) {8 if (! tryAcquire(arg) && 9 acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) 10 selfInterrupt(); 11} 12 protected final Boolean tryAcquire(int acquires) {15 /* 16 * Walkthrough: 17 * 1. If read count nonzero or write count nonzero 18 * and owner is a different thread, fail. 19 * 2. If count would saturate, fail. (This can only 20 * happen if count is already nonzero.) 21 * 3. Otherwise, this thread is eligible for lock if 22 * it is either a reentrant acquire or 23 * queue policy allows it. If so, update state 24 * and set owner. 25 */ 26 Thread current = Thread.currentThread(); 27 int c = getState(); 28 int w = exclusiveCount(c); // Set synchronization state to the lower 16 bits. 29 If (c! = 0) { 30 // (Note: if c ! = 0 and w == 0 then shared count ! = 0) 31 / / read lock or the thread of the current thread is not have access to write lock, 32 if returns false (w = = 0 | | current! = getExclusiveOwnerThread()) 33 return false; 34 // Determine whether the same thread obtains the write lock more than the maximum number of times, 35 If (w + exclusiveCount(acquires) > MAX_COUNT) // 36 throw new Error("Maximum lock count exceeded"); 37 // Reentrant acquire 38 setState(c + acquires); 39 return true; 40} / / 41 c = 0, at this time to read and write locks lock is not get 42 if (writerShouldBlock () | | 43! compareAndSetState(c, c + acquires)) 44 return false; 45 setExclusiveOwnerThread(current); 46 return true; 47}Copy the code

As can be seen from the source code, the steps to obtain the write lock are as follows:

1) Check whether the synchronization state is 0. If the state! If =0, another thread has acquired the read lock or write lock, go to 2). Otherwise, go to 5).

2) Check whether the lower 16 bits (w) of the synchronization state is 0. If w=0, another thread has acquired the read lock, return false; If w! If =0, another thread has acquired the write lock. Go to Step 3.

3) Check whether the current thread acquired the write lock, if not return false, otherwise execute 4);

4) Determine whether the current thread obtains the write lock for more than the maximum number of times, if so, throw the exception, otherwise update the synchronization status (at this time, the current thread obtains the write lock, the update is thread-safe), return true.

5) At this time, neither the read lock nor the write lock is acquired. Determine whether the synchronization status needs to be blocked (the implementation is different between fair and unfair methods). If the synchronization status does not need to be blocked, the CAS updates the synchronization status. Return false if blocking is required.

WriterShouldBlock () indicates whether the current thread should be blocked. NonfairSync and FairSync differ in implementation.

1 //FairSync needs to check whether there is a precursor node, if so return false, otherwise return true. Follow FIFO 2 Final Boolean writerShouldBlock() {3 return hasqueued24 (); 4} 5 6 // return false in NonfairSync. 7 final boolean writerShouldBlock() { 8 return false; // writers can always barge 9 }Copy the code

Release write lock

Public void unlock() {3 sync.release(1); Public final Boolean release(int arg) {8 if (tryRelease(arg)) {9 Node h = head; 10 if (h ! = null && h.waitStatus ! = 0) 11 unparkSuccessor(h); 12 return true; 13 } 14 return false; 17 // Override tryRelease(int releases) {19 if (! isHeldExclusively()) 20 throw new IllegalMonitorStateException(); 21 int nextc = getState() - releases; // Determine whether the low 16 bits of the synchronization state (write synchronization state) is 0 and return true if it is 0. Boolean Free = exclusiveCount(nexTC) == 0; 25 if (free) 26 setExclusiveOwnerThread(null); 27 setState(nextc); // To obtain the write lock, no other synchronization measures are required, is thread safe 28 return free; 29}Copy the code

ReadLock A ReadLock is a reentrant shared lock that adopts the shared strategy of obtaining synchronization state provided by AQS.

(1) Obtain read lock

1 public void lock() { 2 sync.acquireShared(1); Public final void acquireShared(int arg) {7 if (tryAcquireShared(arg) < 0) 8 doAcquireShared(arg); } 2} 2} 2} 2} 3} 3} 3} 12 protected final int tryAcquireShared(int unused) {13 /* 14 * Walkthrough: 15 * 1. If write lock held by another thread, fail. 16 * 2. Otherwise, this thread is eligible for 17 * lock wrt state, so ask if it should block 18 * because of queue policy. If not, try 19 * to grant by CASing state and updating count. 20 * Note that step does not check for reentrant 21 * acquires, which is postponed to full version 22 * to avoid having to check hold count in 23 * the more typical non-reentrant case.  24 * 3. If step 2 fails either because thread 25 * apparently not eligible or CAS fails or count 26 * saturated, chain to version with full retry loop. 27 */ 28 Thread current = Thread.currentThread(); 29 int c = getState(); 30 //exclusiveCount(c) Obtain the lower 16-bit write lock. If a write lock exists and the current thread is not the one acquiring the write lock, -1 is returned, indicating that the read lock fails to be acquired. 31 if (exclusiveCount(c) ! = 0 && 32 getExclusiveOwnerThread() ! = current) 33 return -1; 34 int r = sharedCount(c); ReaderShouldBlock () is used to determine if the current thread should be blocked. 36 if (! ReaderShouldBlock () &&37 r < MAX_COUNT && CompareAndSetState (c, c + SHARED_UNIT)) {39 //firstReader will not be placed in readHolds, thus avoiding looking for readHolds if only one lock is being read. 40 if (r == 0) {// if (r == 0) { 41 firstReader = current; 42 firstReaderHoldCount = 1; 43} else if (firstReader == current) {//firstReader reenter 44 firstReaderHoldCount++; 45} else {46 // Non-firstReader read lock reentrant count update 47 HoldCounter rh = cachedHoldCounter; / / read reentrant lock count caching, based on the ThreadLocal 48 if (rh = = null | | rh. Dar! = current.getId()) 49 cachedHoldCounter = rh = readHolds.get(); 50 else if (rh.count == 0) 51 readHolds.set(rh); 52 rh.count++; 53 } 54 return 1; 55} 56 // The first attempt to acquire the read lock fails. 57 //2) The current thread owns the write lock, and other write locks are waiting for the next node of the current thread to acquire the write lock, unless the next node of the current thread is canceled. 59 return fullTryAcquireShared(current); 60}Copy the code

As can be seen from the source code, the general steps to obtain the read lock are as follows:

1) If a write lock exists and the current thread is not the one acquiring the write lock, -1 is returned according to the judgment that the synchronization status is 16 bits lower, and the read lock fails to be acquired. Otherwise, go to Step 2.

2) Use readerShouldBlock to determine if the current thread should be blocked, and if not, try CAS state synchronization; Otherwise, go to 3).

3) Failed to obtain the read lock for the first time, try again with fullTryAcquireShared.

The readerShouldBlock method is used to determine whether the current thread should be blocked. NonfairSync and FairSync are implemented differently.

1 //FairSync needs to check whether there is a precursor node, if so return false, otherwise return true. Follow FIFO 2 Final Boolean readerShouldBlock() {3 return hasqueued24 (); 4 } 5 final boolean readerShouldBlock() { 6 return apparentlyFirstQueuedIsExclusive(); 7} 8 // Return true if head is not null and the next node s of head is not null and s is in exclusive mode (writer thread) and s is not null. 9 // The goal is that write locks should not be kept waiting. As a heuristic to avoid possible writer thread hunger, this is only a probabilistic effect, because if a waiting writer thread waits behind another reader thread that has not yet been queued, the new reader thread will not be blocked. 10 final boolean apparentlyFirstQueuedIsExclusive() { 11 Node h, s; 12 return (h = head) ! = null && 13 (s = h.next) ! = null && 14 ! s.isShared() && 15 s.thread ! = null; 16}Copy the code

FullTryAcquireShared method

1 final int fullTryAcquireShared(Thread current) { 2 /* 3 * This code is in part redundant with that in 4 * tryAcquireShared but is simpler overall by not 5 * complicating tryAcquireShared with interactions between 6 * retries and lazily reading hold counts. 7 */ 8 HoldCounter rh = null; 9 for (;;) { 10 int c = getState(); 11 // If (exclusiveCount(c)! = 0) { 13 if (getExclusiveOwnerThread() ! = current) 14 return -1; 15 // else we hold the exclusive lock; Blocking here 16 // Would cause deadlock. 17 // If the current thread holds a write lock, other threads are queuing to apply for a write lock. Therefore, even if the thread applying for a write lock already holds a write lock, Lock degradation) will still fail, because other threads are also applying for write locks. At this point, you can only end the request for read locks and queue up instead. Otherwise, deadlock will occur. 18 } else if (readerShouldBlock()) { 19 // Make sure we're not acquiring read lock reentrantly 20 if (firstReader == Assert firstReaderHoldCount > 0; assert firstReaderHoldCount > 0; 23} else {24 // Remove the current thread's hold count from readHolds, return -1, and queue for read locks. 25 if (rh == null) { 26 rh = cachedHoldCounter; 27 if (rh == null || rh.tid ! = current.getId()) { 28 rh = readHolds.get(); 29 if (rh.count == 0) 30 readHolds.remove(); 31 } 32 } 33 if (rh.count == 0) 34 return -1; 35 } 36 } 37 if (sharedCount(c) == MAX_COUNT) 38 throw new Error("Maximum lock count exceeded"); 39 if (compareAndSetState(c, c + SHARED_UNIT)) {  41 if (sharedCount(c) == 0) { 42 firstReader = current; 43 firstReaderHoldCount = 1; 44 } else if (firstReader == current) { 45 firstReaderHoldCount++; 46 } else { 47 if (rh == null) 48 rh = cachedHoldCounter; 49 if (rh == null || rh.tid ! = current.getId()) 50 rh = readHolds.get(); 51 else if (rh.count == 0) 52 readHolds.set(rh); 53 rh.count++; 54 cachedHoldCounter = rh; // cache for release 55 } 56 return 1; 57} 58} 59}Copy the code

(2) Release read lock

1 public void unlock() { 2 sync.releaseShared(1); 3 } 4 5 public final boolean releaseShared(int arg) { 6 if (tryReleaseShared(arg)) { 7 doReleaseShared(); 8 return true; 9 } 10 return false; 11 } 12 13 protected final boolean tryReleaseShared(int unused) { 14 Thread current = Thread.currentThread(); If (firstReader == current) {17 // Assert firstReaderHoldCount > 0; 18 if (firstReaderHoldCount == 1) 19 firstReader = null; 20 else 21 firstReaderHoldCount--; 22 } else { 23 HoldCounter rh = cachedHoldCounter; 24 if (rh == null || rh.tid ! = current.getId()) 25 rh = readHolds.get(); 26 int count = rh.count; 27 if (count <= 1) { 28 readHolds.remove(); 29 if (count <= 0) 30 throw unmatchedUnlockException(); 31 } 32 --rh.count; } 34 // CAS, minus 1<<16 35 for (;;) { 36 int c = getState(); 37 int nextc = c - SHARED_UNIT; 38 if (compareAndSetState(c, nextc)) 39 // Releasing the read lock has no effect on readers, 40 // but it may allow waiting writers to proceed if 41 // both read and write locks are now free. 42 return nextc == 0;  44 43}}Copy the code

Mysql, Netty, Spring, thread, Spring Cloud, JVM, source code, algorithm, etc., also have a detailed learning plan map, interview questions, etc., need to obtain these contents of the friend please add Q: sample: 909038429/./* Welcome to Java chat