Source code analysis of jdk1.8 ReentrantReadWriteLock

ReentrantReadWriteLock

ReentrantReadWriteLock is a reentrant read-write lock. Next, we will analyze how it works through the source code

Important attribute

/** Inner class providing readlock */
 private final ReentrantReadWriteLock.ReadLock readerLock;
 /** Inner class providing writelock */
 private final ReentrantReadWriteLock.WriteLock writerLock;
 /** Performs all synchronization mechanics */
 final Sync sync;

ReentrantLockReadWriteLock has the following important properties:
(1) like other AQS components, there is a Sync object for underlying concurrency control
(2) ReadLock is an internal class of ReentrantLockReadWriteLock, which represents a read lock
(3) WriteLock is an internal class of ReentrantLockReadWriteLock, which represents a write lock

Constructor

public ReentrantReadWriteLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
    readerLock = new ReadLock(this);
    writerLock = new WriteLock(this);
}

Constructor takes a boolean type parameter to represent whether fair lock is implemented. See previous blogs for how fair lock and unfair lock are implemented

ReadLock

Important attribute

private final Sync sync;

Constructor

protected ReadLock(ReentrantReadWriteLock lock) {
    sync = lock.sync;
}

The ReadLock constructor passes in the ReentrantLockReadWriteLock object, which uses the object's concurrency controller as its own concurrency controller

Other important functions

public void lock() {
    sync.acquireShared(1);
}
public boolean tryLock() {
    return sync.tryReadLock();
}
public Condition newCondition() {
    throw new UnsupportedOperationException();
}
public void unlock() {
   sync.releaseShared(1);
}

ReadLock is a shared lock because lock() calls acquireshard()

WriteLock

Important attribute

private final Sync sync;

Constructor

protected WriteLock(ReentrantReadWriteLock lock) {
    sync = lock.sync;
}
```
WriteLock Constructors and ReadLock Similar
### Other important functions
```java
public void lock() {
    sync.acquire(1);
}
public Condition newCondition() {
    return sync.newCondition();
}
//  thread has a hold on a lock for each lock action that is not matched by an unlock action.
public int getHoldCount() {
    return sync.getWriteHoldCount();
}
// Determine whether the lock is occupied exclusively by the current thread
public boolean isHeldByCurrentThread() {
    return sync.isHeldExclusively();
}
```
lock()What is called isacquire()´╝îtherefore WriteLock It's exclusive lock.

## Sync
//Let's take a look at the specific implementation of Sync
### Important attribute
```java
static final int SHARED_SHIFT   = 16;
static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
// First reader thread
private transient Thread firstReader = null;
// Number of first reader re entrances
private transient int firstReaderHoldCount;
// Recent readership count
private transient HoldCounter cachedHoldCounter;
/** Returns the number of shared holds represented in count  */
static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
/** Returns the number of exclusive holds represented in count  */
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
```

ReentrantLock Will one32BitintVariable of type state It is divided into two parts, both of which are occupied separately16Bit high16Bit is used to store the status of read lock, low16Bits are used to store the status of a write lock
![Insert picture description here](https://img-blog.csdnimg.cn/20190331203441219.png)

//In addition, the number of re entrances of each read thread will be recorded here. If it is the first reader, the number of re entrances will be set in ReentrantReadWriteLock. If it is the latest reader, it will also be cached in ReentrantReadWriteLock
//Other readers will use ThreadLocal to set each thread
### Shared operation corresponding to ReadLock
#### Obtain
```java
protected final int tryAcquireShared(int unused) {
	// Get current thread
   Thread current = Thread.currentThread();
   // Get the status of the concurrent controller
   int c = getState();
   // If the current write lock is occupied and the process that occupies the write lock is not the current thread, then obtaining the read lock fails
   // There is a special case, that is, if the current thread has acquired the write lock before, it can still acquire the read lock
   if (exclusiveCount(c) != 0 &&
       getExclusiveOwnerThread() != current)
       return -1;
	// Get the status of the read lock, that is, how many threads are reading
   int r = sharedCount(c);
   // First, execute readerShouldBlock, which is an abstract method. It is implemented differently according to whether ReentrantLock is fair or unfair
   // Then judge whether the current reading is less than the upper limit of reading
   // Then, cas is used to compete for resources and increase the number of reads. In fact, the higher 16 bits are treated as an integer, and then 1 is added
   // If it is fair mode and there are elements in the team, readerShouldBlock will return true
  	// The condition returns true: readers should not be blocked. The current number of readers does not exceed the maximum, and the resource quantity has been updated successfully
   if (!readerShouldBlock() &&
       r < MAX_COUNT &&
       compareAndSetState(c, c + SHARED_UNIT)) {
       // If the current thread is the first reader
       // Set current thread as first reader
       // And update the first reader's reentry times
       if (r == 0) {
       		// The current thread is the first reader
           firstReader = current;
           firstReaderHoldCount = 1;
       } else if (firstReader == current) {
       		// The current thread is the first reader, but it is reentrant
           firstReaderHoldCount++;
       } else {
       		// Current thread is not the first reader
       		// Use cachedHoldCounter to represent the number of recent readership
       		// The reason why cachedHoldCounter is used here is that if a thread is rereading all the time or releasing a read lock, using cache can speed up the processing speed
           HoldCounter rh = cachedHoldCounter;
           if (rh == null || rh.tid != getThreadId(current))
               cachedHoldCounter = rh = readHolds.get();
           else if (rh.count == 0)
               readHolds.set(rh);
           rh.count++;
       }
       return 1;
   }
   // It may be that the read lock should be blocked, the number of read locks exceeds the upper limit, or it may be failed to update the status using cas. Next, use the method of adding cas circularly to try to set
   return fullTryAcquireShared(current);
}
```

```java
final int fullTryAcquireShared(Thread current) {
   /*
    * This code is in part redundant with that in
    * tryAcquireShared but is simpler overall by not
    * complicating tryAcquireShared with interactions between
    * retries and lazily reading hold counts.
    */
   HoldCounter rh = null;
   for (;;) {
   		// Acquisition state
       int c = getState();
       // If a thread is currently writing, and the thread that occupies the write lock is not the current thread, end the loop and return - 1
       // Delegate can't get read lock
       if (exclusiveCount(c) != 0) {
           if (getExclusiveOwnerThread() != current)
               return -1;
           // else we hold the exclusive lock; blocking here
           // would cause deadlock.
       } else if (readerShouldBlock()) {
           // Make sure we're not acquiring read lock reentrantly
           if (firstReader == current) {
               // assert firstReaderHoldCount > 0;
           } else {
           		// Current thread is not the first reader
               if (rh == null) {
                   rh = cachedHoldCounter;
                   // Gets the number of read reentries for the current thread
                   if (rh == null || rh.tid != getThreadId(current)) {
                       rh = readHolds.get();
                       if (rh.count == 0)
                           readHolds.remove();
                   }
               }
               if (rh.count == 0)
                   return -1;
           }
       }
       // The number of read locks exceeds the upper limit
       if (sharedCount(c) == MAX_COUNT)
           throw new Error("Maximum lock count exceeded");
       // Try competing resources again
       if (compareAndSetState(c, c + SHARED_UNIT)) {
       		// Competitive success
       		// The current thread becomes the first read lock
           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;
       }
   }
}
```
//Let's take a lookreaderShouldBlock()
//First of all, under the fair model
//If there are elements in the waiting queue, returntrue
FairSync
```java
final boolean readerShouldBlock() {
    return hasQueuedPredecessors();
}
```
//And then look at the unfair model
NonFairSync
```java
final boolean readerShouldBlock() {
    /* As a heuristic to avoid indefinite writer starvation,
     * block if the thread that momentarily appears to be head
     * of queue, if one exists, is a waiting writer.  This is
     * only a probabilistic effect since a new reader will not
     * block if there is a waiting writer behind other enabled
     * readers that have not yet drained from the queue.
     */
    return apparentlyFirstQueuedIsExclusive();
}
```
//Here is the source code in AQS
//The following function returnstrueThe condition is that there are nodes in the waiting queue, and the first task waiting to run is an exclusive task, that is, write task
//In unfair mode, as long as the first task waiting to run is not a write task, the current thread can successfully obtain a read lock
//The goal is to prevent write threads from starving
```java
final boolean apparentlyFirstQueuedIsExclusive() {
   Node h, s;
   return (h = head) != null &&
       (s = h.next)  != null &&
       !s.isShared()         &&
       s.thread != null;
}
```
#### release
```java
protected final boolean tryReleaseShared(int unused) {
	// Get the current thread first
   Thread current = Thread.currentThread();
   // The current thread is the first reader
   if (firstReader == current) {
       // assert firstReaderHoldCount > 0;
       // Reduce the number of first reader re entrances
       if (firstReaderHoldCount == 1)
           firstReader = null;
       else
           firstReaderHoldCount--;
   } else {
   		// Current thread is not the first reader
       HoldCounter rh = cachedHoldCounter;
       // Only the first and most recent reader's reentry times are recorded in the current structure
       // The number of reentry times of other readers needs to be obtained through ThreadLocal
       if (rh == null || rh.tid != getThreadId(current))
           rh = readHolds.get();
       int count = rh.count;
       // Reduce the number of read reentries for the current thread
       if (count <= 1) {
           readHolds.remove();
           if (count <= 0)
               throw unmatchedUnlockException();
       }
       --rh.count;
   }
   // Loop cas to update status
   for (;;) {
       int c = getState();
       int nextc = c - SHARED_UNIT;
       if (compareAndSetState(c, nextc))
           // Releasing the read lock has no effect on readers,
           // but it may allow waiting writers to proceed if
           // both read and write locks are now free.
           return nextc == 0;
   }
}
```
### Exclusive operation corresponding to WriteLock
#### Obtain
```java
protected final boolean tryAcquire(int acquires) {
    /*
     * Walkthrough:
     * 1. If read count nonzero or write count nonzero
     *    and owner is a different thread, fail.
     * 2. If count would saturate, fail. (This can only
     *    happen if count is already nonzero.)
     * 3. Otherwise, this thread is eligible for lock if
     *    it is either a reentrant acquire or
     *    queue policy allows it. If so, update state
     *    and set owner.
     */
     // Get current thread
    Thread current = Thread.currentThread();
    int c = getState();
    // Get write status
    int w = exclusiveCount(c);
    // Have readers or writers
    // Note that even if the current thread has obtained a read lock, it still cannot write directly
    // On the contrary, if the current thread has obtained a write lock, it can read
    if (c != 0) {
        // (Note: if c != 0 and w == 0 then shared count != 0)
        // There are readers who write that it is not the current thread, and the competition fails
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;
        // The number of writers exceeds the maximum
        if (w + exclusiveCount(acquires) > MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        // Reentrant acquire
        // The current thread has occupied the write lock, so it is a write in and update status
        setState(c + acquires);
        return true;
    }
    // Judge whether the current write lock needs to be blocked according to whether it is fair or not
    // Or failed to update resources
    if (writerShouldBlock() ||
        !compareAndSetState(c, c + acquires))
        return false;
    setExclusiveOwnerThread(current);
    return true;
}
```
//Let's take a lookwriteShouldBlock()
//First, under the fair model
FairSync
```java
static final class FairSync extends Sync {
   private static final long serialVersionUID = -2274990926593161451L;
    // Other threads in the waiting queue are waiting, return false
    final boolean writerShouldBlock() {
        return hasQueuedPredecessors();
    }
}
```
//And then it's not fair
NonFairSync
//Direct returnfalse
```java
final boolean writerShouldBlock() {
     return false; // writers can always barge
 }
```
#### release
```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;
}
```

### Lock down
//Lock degradation refers to that a thread can continue to occupy the read lock when the write lock is exclusive, and when the write lock is released, the thread still occupies the read lock.
//So what is the main manifestation of lock degradation?
//This is mainly reflected in the competition for the read lock tryAcquieShare. If a thread acquires the write lock, it does not immediately return to the competition failure, but first judges whether the thread acquiring the write lock is on the front line. If the current thread occupies the write lock, it can continue to compete for the read lock normally. If the current thread occupies the write lock, it directly returns to the competition failure
//So what's the use of lock degradation? Here's an online quote to explain why
//The main purpose is to ensure the visibility of data. If the current thread does not acquire the read lock and releases it directly, assume that at this time, another thread (sending thread T) acquires the write lock and changes the data, then the current thread cannot perceive the data update of thread t. If the current thread obtains the read lock in the way of lock degradation, thread t cannot obtain the write lock successfully and block it because it judges that a thread is occupying the read lock. Until the current thread uses data and releases the read lock and the write lock, thread t can use the write lock

Tags: Java Attribute less

Posted on Sat, 07 Dec 2019 06:05:17 -0500 by Chunk1978