One, foreword

ReentrantLock (two) analysis of ReentrantLock to implement AQS synchronizer, through AQS source analysis, we know that synchronizer through SATE state lock acquisition and release, and the construction of a bi-directional FIFO bi-directional list thread node waiting, The thread node uses waitStatus to determine whether it needs to suspend or wake up to acquire the lock. ReentrantReadWriteLock ReentrantReadWriteLock ReentrantReadWriteLock

Overview of ReentrantReadWriteLock

ReentrantReadWriteLock lock is also inherited from the AQS class to implement the lock function, the last article Java lock ReentrantLock (2) has been detailed parsing of THE IMPLEMENTATION of AQS, if you have mastered the principle of AQS, I believe that the following read and write lock parsing is also very easy.

  • ReentrantReadWriteLock Specifies the internal class list of a lock
class role
The Sync. Inheriting AQS, the main implementor of the lock function
FairSync Inherit Sync to implement fair locking
NofairSync Inherit Sync, mainly to implement unfair locking
ReadLock Read the lock, through the Sync agent to achieve the lock function
WriteLock Write lock, through the sync agent to achieve the lock function

A ReentrantReadWriteLock lock depends on a state variable. A state variable can be used to distinguish a read lock from a write lock. A state variable can be used to distinguish a read lock from a write lock. The answer is by bit operation, with the higher 16 bits representing a read lock and the lower 16 bits representing a write lock. If you are not familiar with bitwise operations or not familiar with bitwise operations, check out this article bitwise operations. Since is the analysis of read and write lock, so we first from the read lock and write lock source access analysis.

Here is a concept added in advance:

The write lock and the read lock are mutually exclusive (the mutual exclusion here refers to the mutual exclusion between threads, the current thread can acquire the write lock and the read lock, but the read lock can not continue to acquire the write lock), this is because the write lock to maintain the visibility of the write operation, if the read lock is allowed to acquire the write lock under the condition of being acquired. Other running reader threads are not aware of the current writer thread’s actions. Therefore, the write lock cannot be acquired by the current thread until all other threads have released the read lock. Once the write lock is acquired, all subsequent access by other read-write threads will be blocked.

  • Write lock tryLock ()

TryWriteLock () is the final call to write the lock (take the non-blocking lock method as an example).

 public boolean tryLock() {return sync.tryWriteLock();
        }
        
        
 final boolean tryWriteLock() {
            Thread current = Thread.currentThread();
            int c = getState();
            if(c ! Int w = exclusiveCount(c); int w = exclusiveCount(c); // If the lock is reentrant, the lock is reentrant. = 0 is a lock is obtained, then w = = 0, / / write lock is has not been obtained, that is to say, to read lock is obtained, due to write and read lock mutex lock, in order to guarantee the data visibility / / soreturnfalse. //2. w! =0, write lock was acquired, but current! = getExclusiveOwnerThread(),return false;
                if(w == 0 || current ! = getExclusiveOwnerThread())return false;
                if(w == MAX_COUNT)// Throw new Error("Maximum lock count exceeded"); } // Try to obtain the lockif(! compareAndSetState(c, c + 1))return false;
            setExclusiveOwnerThread(current);
            return true;
        }
Copy the code
  • Read lock tryLock ()Again, let’s look at non-blocking lock acquisition methods,tryReadLock()
 final boolean tryReadLock() {
            Thread current = Thread.currentThread();
            for (;;) {
                int c = getState();
                if(exclusiveCount(c) ! = 0 && getExclusiveOwnerThread() ! = current)return false; // The write lock was acquired by another threadfalseint r = sharedCount(c); // Get the status of the read lockif (r == MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
                if(compareAndSetState(c, c + SHARED_UNIT)) {// Try to obtain the read lockif(r == 0) {firstReader = current; // mark the current thread as the first to fetch firstReaderHoldCount = 1; // reentrant count}else if(firstReader == current) { firstReaderHoldCount++; // count +1}else{//cachedHoldCounter is the last thread in the cache to acquire the lock.if(rh == null || rh.tid ! = getThreadId(current)) cachedHoldCounter = rh =readHolds.get(); // Cache the last thread to acquire the lockelse if(rh.count == 0)// The current thread has acquired the lock, but the reentrant count is 0, then the current thread stores inreadHolds.set(rh);
                        rh.count++;
                    }
                    return true; }}}Copy the code
  • TryReleaseShared ()

Write lock release is relatively simple, the basic logic and read lock release is the same, considering the length, this time mainly analyze read lock release process:

 protected final boolean tryReleaseShared(int unused) {
            Thread current = Thread.currentThread();
            if (firstReader == current) {
                // assert firstReaderHoldCount > 0;
                ifFirstReaderHoldCount == 1 firstReaderHoldCount == 1 firstReader = nullelse
                    firstReaderHoldCount--;
            } else {
                HoldCounter rh = cachedHoldCounter;
                if(rh == null || rh.tid ! = getThreadId(current)) rh =readHolds.get();
                int count = rh.count;
                if(count <= 1) {// Indicates that all data is releasedreadHolds.remove(); // Remove the number of records saved for so longif (count <= 0)
                        throw unmatchedUnlockException();
                }
                --rh.count;
            }
            for(;;) { int c = getState(); Int nexTC = c-shared_unit; int nexTC = c-shared_unit;if(compareAndSetState(C, NexTC)returnnextc == 0; // If the value of the upper 16 bits is ==0, then the read state and the write state are released}}Copy the code

Above is read and write lock acquisition and release process source code, the first analysis of simple non-blocking lock acquisition method, according to the source code we can know, write lock and read lock whether to obtain is also to judge whether the state is not 0, write lock state acquisition method is exclusiveCount(c), read lock state acquisition method is sharedCount(C). Then we will analyze how these two methods operate on a single variable bit to obtain their respective states. Before analysis, we will summarize the previous content.

  • The subtotal

A. Read/write locks are distinguished by the bit operation of the State variable of AQS. 16 bits higher represent read locks and 16 bits lower represent write locks.

B. To ensure visibility between threads, read locks and write locks are mutually exclusive. The mutual exclusion refers to the mutual exclusion between threads. The current thread can obtain both the write lock and the read lock, but cannot obtain the write lock after obtaining the read lock.

Analysis of Sync bit operation

  • Schematic diagram of state variables divided by bits

Let’s take a look at the bit code (I assume you already know the basics of bit arithmetic, if not, please read bit arithmetic)

static final int SHARED_SHIFT = 16; Static final int SHARED_UNIT = (1 << SHARED_SHIFT); // Max 65535 static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1; // same 65535 static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1; Static int sharedCount(int c) {returnc >>> SHARED_SHIFT; Static int exclusiveCount(int c) {return c & EXCLUSIVE_MASK; }

Copy the code

Our operations of the data of the content, as shown here is a 32-bit binary data is: 00000000000000100000000000000011

  • Read State acquisition

00000000000000100000000000000011 > > > 16, unsigned right shift 16, the results are as follows: 00000000000000000000000000000010, converted to decimal number equal to 2, shows that reading status to: 2

  • Read State acquisition

00000000000000100000000000000011 & 65535, converted to hexadecimal operation for 00000000000000100000000000000011 & 00000000000000001111111111111111

Finally with the operation results for: 00000000000000100000000000000011, converted to decimal is 3

I have to admire the idea of the author. This design satisfies the separation of read lock and write lock only through the original State variable without modifying the code of AQS.

4. Lock degradation

Lock degradation refers to the degradation of a write lock to a read lock. If the current thread owns the write lock, then releases it, and finally acquires the read lock, this piecewise completion process is not called lock degradation. Lock degradation refers to holding (previously owned lock writing process) :

public void processData() {readLock.lock();
    if(! Update){// The read lock must be released firstreadLock.unlock(); // lock degradation from writeLock to start writelock.lock (); try{if(! update){ update =true; } readlock.lock(); }finally{ writeLock.unlock(); } try{// skip}finally{readLock.unlock(); }}Copy the code

The above example is a lock degradation process. Note that the update variable is a volatie variable, so it is visible between threads. This code is to obtain the write lock after modifying variables, and then obtain the read lock, after obtaining a successful release of the write lock, complete the degradation of the lock. Note: ReentrantReadWriteLock does not support lock upgrades. This is because if multiple threads acquire a read lock and any of them acquire a write lock and modify the data, the other threads will not be aware of the update and thus cannot ensure the visibility of the data.

The final summary

  • In the source code, other parts are involved, and this article has been simplified, such as:cachedHoldCounter.firstReader firstReaderHoldCountThese attributes do not have much impact on understanding principles, but mainly improve performance, so they are not discussed in this paper.
  • Read/write locking relies on AQS ‘custom synchronizer to implement it. Most of the code is similar to that in the previous two articles “Java Lock ReentrantLock”. Most of the ANALYSIS of AQS has been resolved in these two articles.
  • The clever design of read/write lock is to calculate the lock state of AQS and distinguish the read state from the write state.