From the school to A factory all the way sunshine vicissitudes of life

Please go to www.codercc.com

1. Read/write lock

To address thread safety issues in concurrent scenarios, we use exclusive locks almost all the time, You typically use the Java keyword synchronized (see this article for synchronized) or ReentrantLock, which implements the Lock interface in the ConCurrents package. They are exclusive locks, meaning that only one thread can acquire the lock at a time. In some business scenarios, most of the data is read, but very little data is written. If the data is read only, data correctness is not affected (dirty reads occur), and if the exclusive lock is still used in such a business scenario, this is obviously a performance bottleneck. Java also provides another ReentrantReadWriteLock(read/write Lock) that implements the Lock interface. Read-write allows access by multiple reader threads at the same time, but all reader threads and other writer threads are blocked when the writer thread accesses them. The mutual exclusion of WirteLock and ReadLock can be analyzed in terms of WriteLock to WriteLock, WriteLock to ReadLock, and ReadLock to ReadLock. More about read and write lock features we can see the source code on the introduction (read the source code is the best way to learn, I am also learning, with everyone encouraged), here to make a summary:

  1. Fair selection: support unfair (default) and fair lock acquisition methods, throughput or unfair is better than fair;
  2. Reentrant: Support reentrant, read lock can be acquired again, write lock can be acquired again, but also can be acquired read lock;
  3. Lock degradation: A write lock can be degraded to a read lock by following the sequence of acquiring a write lock, acquiring a read lock, and releasing the write lock

To thoroughly understand read/write locks, you must understand the following questions: 1. How do read/write locks record read/write states separately? 2. How are write locks acquired and released? 3. How are read locks acquired and released? Let’s take a look at read-write locks with these three questions in mind.

2. Write lock details

2.1. Write lock acquisition

The implementation of synchronous components aggregates the synchronizer (AQS) and implements the synchronization semantics of synchronous components by overriding the methods in the synchronizer (AQS) (see this article for an implementation hierarchy of synchronous components and an analysis of the underlying implementation of AQS). Therefore, write locks are still implemented this way. A write lock cannot be acquired by multiple threads at the same time. It is obvious that the write lock is an exclusive lock, and the synchronization semantics of the write lock are implemented by overwriting the tryAcquire method in AQS. The source code for:

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. */ Thread current = Thread.currentThread(); Int c = getState(); Int w = exclusiveCount(c); if (c ! = 0) { // (Note: if c ! = 0 and w == 0 then shared count ! = 0) / / 3.1 when a read lock has been thread or the current thread is not thread fetching the write lock has failed to get write lock / / if the current thread if (w = = 0 | | current! = getExclusiveOwnerThread()) return false; if (w + exclusiveCount(acquires) > MAX_COUNT) throw new Error("Maximum lock count exceeded"); SetState (c + acquires); // Reentrant acquire // return true; } / / 3.3 write lock has not been any thread, the current thread can get write locks the if (writerShouldBlock () | |! compareAndSetState(c, c + acquires)) return false; setExclusiveOwnerThread(current); return true; }Copy the code

ExclusiveCount (c) : exclusiveCount(c)

static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
Copy the code

Static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) – 1; EXCLUSIVE _MASK is 1 moved 16 bits to the left and then reduced by 1 to 0x0000FFFF. The exclusiveCount method matches the synchronization state (state is int) with 0x0000FFFF, that is, the lower 16 bits of the synchronization state. So what does the lower 16 mean? The ‘exclusiveCount’ method is used to specify the number of write locks acquired. The lower 16 bits of the synchronization state represent the number of write locks acquired. There is another method worth noting:

static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
Copy the code

Int c (int c, int c, int c, int c, int c, int c); Now remember the first question we had to understand at the beginning? The answer to the question of how a read-write lock records the state of a read-write lock and the state of a write lock is now clear, as shown in the following diagram:

Now let’s go back to tryAcquire. The main logic of tryAcquire is as follows: When the read lock has been acquired by the reader thread or the write lock has been acquired by another writer thread, the write lock fails to be acquired. Otherwise, get success and support reentrant, increase write state.

2.2. Release of write lock

Write lock release by overriding AQS tryRelease method

protected final boolean tryRelease(int releases) { if (! isHeldExclusively()) throw new IllegalMonitorStateException(); Int nexTC = getState() -releases; If the write status is 0, release the write lock. Boolean Free = exclusiveCount(nexTC) == 0; if (free) setExclusiveOwnerThread(null); //3. If the value is not 0, update the synchronization state setState(nexTC); return free; }Copy the code

Reduce write state int nexTC = getState() -releases; The reason we just subtract the write state directly from the current synchronization state is because the write state we just said is represented by the lower 16 bits of the synchronization state.

3. Read lock details

3.1. Read lock acquisition

Read locks are not exclusive locks that can be acquired by multiple readers at the same time. This is a shared lock. In accordance with the introduction of AQS, we need to rewrite AQS methods tryAcquireShared and tryReleaseShared to implement the synchronization semantics of shared synchronized components. The method of obtaining read lock is as follows:

protected final int tryAcquireShared(int unused) { /* * Walkthrough: * 1. If write lock held by another thread, fail. * 2. Otherwise, this thread is eligible for * lock wrt state, so ask if it should block * because of queue policy. If not, try * to grant by CASing state and updating count. * Note that step does not check for reentrant * acquires, which is postponed to full version * to avoid having to check hold count in * the more typical non-reentrant case. * 3. If step 2 fails either because thread * apparently not eligible or CAS fails or count * saturated, chain to version with full retry loop. */ Thread current = Thread.currentThread(); int c = getState(); If (exclusiveCount(c)! = 0 && getExclusiveOwnerThread() ! = current) return -1; int r = sharedCount(c); if (! readerShouldBlock() && r < MAX_COUNT && //2. CompareAndSetState (c, c + SHARED_UNIT)) {//3. If (r == 0) {firstReader = current; firstReaderHoldCount = 1; } else if (firstReader == current) { firstReaderHoldCount++; } else { 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; } // return fullTryAcquireShared(current); }Copy the code

Note that when the write lock is acquired by another thread, the read lock fails to be acquired. Otherwise, CAS is used to update the synchronization status. In addition, the reason for the current synchronization state is to add SHARED_UNIT ((1 << SHARED_SHIFT) 0x00010000) this is because the higher 16 bits of the synchronization state we mentioned above are used to indicate the number of times the read lock was acquired. If the CAS fails or the thread that has already acquired the read lock acquires the read lock again using the fullTryAcquireShared method, this code will not be developed.

3.2. Release the read lock

TryReleaseShared read lock release is implemented through the tryReleaseShared method.

protected final boolean tryReleaseShared(int unused) { Thread current = Thread.currentThread(); If (firstReader == current) {// Assert firstReaderHoldCount > 0; if (firstReaderHoldCount == 1) firstReader = null; else firstReaderHoldCount--; } else { 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; } for (;;) { int c = getState(); Int nexTC = c-shared_unit; 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; }}Copy the code

4. Lock relegation

A write lock can be degraded to a read lock, and a write lock cannot be upgraded. The following example is from the ReentrantWriteReadLock source code:

void processCachedData() {
        rwl.readLock().lock();
        if (!cacheValid) {
            // Must release read lock before acquiring write lock
            rwl.readLock().unlock();
            rwl.writeLock().lock();
            try {
                // Recheck state because another thread might have
                // acquired write lock and changed state before we did.
                if (!cacheValid) {
                    data = ...
            cacheValid = true;
          }
          // Downgrade by acquiring read lock before releasing write lock
          rwl.readLock().lock();
        } finally {
          rwl.writeLock().unlock(); // Unlock write, still hold read
        }
      }
 
      try {
        use(data);
      } finally {
        rwl.readLock().unlock();
      }
    }
}Copy the code