ReentrantReadWriteLock source code

The introduction

ReentrantLock and ReentrantReadWriteLock can be said to be twin brothers, the front is an exclusive lock, the latter is a shared lock ReentrantLock source code

Classes that inherit AQS need to use the state variable to represent a resource. In ReentrantReadWriteLock, the high 16 bits of state indicate the number of read locks. The lower 16 bits represent the state of the write lock.

1. ReentrantReadWriteLock Implementation of read and write locks

ReentrantReadWriteLock has two more important properties in the source code, as shown below.

Looking at its constructor, you can see that ReentrantReadWriteLock also has fair and unfair locks. By default, it is unfair. Like ReentrantLock, it is implemented through the Sync inner class.

They are two static inner classes for their implementation

abstract static class Sync extends AbstractQueuedSynchronizer {}

public static class ReadLock implements Lock.java.io.Serializable {
    private final Sync sync; 
    protected ReadLock(ReentrantReadWriteLock lock) {
         sync = lock.sync;
    }
    public void lock(a) {
        sync.acquireShared(1); / / Shared
    }
    public void unlock(a) {
        sync.releaseShared(1); / / Shared}}public static class WriteLock implements Lock.java.io.Serializable {
    private final Sync sync;
    protected WriteLock(ReentrantReadWriteLock lock) {
        sync = lock.sync;
    }
    public void lock(a) {
        sync.acquire(1); / / exclusive
    }
    public void unlock(a) {
        sync.release(1); / / exclusive}}Copy the code

As you can see, ReentrantLock and ReentrantReadWriteLock are both implemented based on AQS. The difference is that WriteLock, like ReentrantLock, uses exclusive locks. ReadLock, like Semaphore, uses shared locks.

2, ReentrantReadWriteLock fair lock and unfair lock source code

As you can see from the figure above, ReentrantReadWriteLock is implemented primarily through their common parent, Sync. Before we move on to Sync, let me introduce youreaderShouldBlockandwriterShouldBlockThe role of.

The writerShouldBlock and readerShouldBlock methods both say whether to block when another thread is trying to acquire the lock.

Hasqueuedtoraise, an old friend of the ReentrantLock fair Lock, is used to determine whether the current thread is the first in a queue, and returns true if it is, and false if it is not.

In unfair mode, writerShouldBlock returns false, indicating that no blocking is required; While readShouldBlock calls the apparentFirstQueuedIsExcluisve () method. This method returns true if the current thread is occupied by the write lock; Otherwise return false. This means that if a writer thread is currently writing, the reader thread should block.

3, read lock acquisition

Check the process for obtaining read locks

Lock acquisition is shown in the following figure. When the tryAcquireShared() method is less than 0, the doAcquireShared method is executed to add the thread to the wait queue.

Sync implements the tryAcquireShared method as follows:

protected final int tryAcquireShared(int unused) {
            Thread current = Thread.currentThread();
            int c = getState();
     // If there is a writer thread and this thread is not a writer thread, it does not qualify for reentrant and fails
            if(exclusiveCount(c) ! =0&& getExclusiveOwnerThread() ! = current)return -1;
     // Get the number of read locks
            int r = sharedCount(c);
     // 1. Determine whether to block
     // Fair lock and unfair lock judge blocking way is not the same, specific view fair lock and unfair lock source code
     // 2. Check whether the number of read locks is less than 65535
     // 3.CAS updates the read lock value
            if(! readerShouldBlock() && r < MAX_COUNT && compareAndSetState(c, c + SHARED_UNIT)) {// Initialize the read lock if it is 0
                if (r == 0) {
                    firstReader = current;
                    firstReaderHoldCount = 1;
     		// Since it is a reentrant lock, the counter +1 is read when it is rushed
                } else if (firstReader == current) {
                    firstReaderHoldCount++;
     		// The current reader thread is different from the first reader thread
                } 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;
            }
     // Otherwise, when does the loop attempt loop attempt exchange
     // 1. The lock is fair and other threads are queued
     // 2. If the number of read locks is greater than =65535, wait for other locks to be released
     // 3.CAS fails
            return fullTryAcquireShared(current);
        }
Copy the code
  • cachedHoldCounterIs the last thread counter to acquire the read lock, and is updated every time a new thread acquires the read lock. The purpose of this variable is to be used when the last thread to acquire the read lock repeatedly acquires or releases the read lock. This variable is faster and is equivalent to caching. And each thread has its own counter.

The fullTryAcquireShared method is as follows

final int fullTryAcquireShared(Thread current) {
            HoldCounter rh = null;
            for (;;) {
                int c = getState();
             // Once another thread has acquired the write lock, return -1, failure
                if(exclusiveCount(c) ! =0) {
                    if(getExclusiveOwnerThread() ! = current)return -1;
             // If the reader thread needs to block
                } else if (readerShouldBlock()) {
                    if (firstReader == current) {
             // Another reader thread is holding the lock
                    } else {
                        if (rh == null) {
                            rh = cachedHoldCounter;
                            if (rh == null|| rh.tid ! = getThreadId(current)) { rh = readHolds.get();if (rh.count == 0) readHolds.remove(); }}if (rh.count == 0)
                            return -1; }}// If the read lock reaches the maximum value, an exception is thrown
                if (sharedCount(c) == MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
              // If the status is successfully changed, success returns
                if (compareAndSetState(c, c + SHARED_UNIT)) {
              // When the read thread is different from the first read thread, the number of reads for each thread is recorded
                    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; }}}Copy the code

You can see that fullTryAcquireShared has a lot in common with tryAcquireShared.

As you can see above, the readerShouldBlock method is called multiple times. For a fair lock, as long as there are threads waiting in the queue, this should return true, which means that the reader thread should block. For unfair locks, return true if the current thread has acquired the write lock. Once unblocked, the reader thread will have a chance to acquire the read lock.

4. Write lock acquisition

The lock method is as follows: As you can see above, write locks use the EXCLUSIVE mode of AQS. The first attempt is to acquire the lock, and if it fails, the thread is added to the wait queue.

protected final boolean tryAcquire(int acquires) {
            Thread current = Thread.currentThread();
            int c = getState();
    // Get the number of write locks
            int w = exclusiveCount(c);
    // If there is a write lock or a read lock
            if(c ! =0) {
            // Failed to acquire the lock if the write lock is 0 or if the current thread is equal to a non-exclusive thread
                if (w == 0|| current ! = getExclusiveOwnerThread())return false;
            // Write lock exceeds the maximum value
                if (w + exclusiveCount(acquires) > MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
           // Setting the status succeeded
                setState(c + acquires);
                return true;
            }
    // If there is no current write lock or read lock, the writer thread should block or CAS failed, returning false
            if(writerShouldBlock() || ! compareAndSetState(c, c + acquires))return false;
    // Otherwise set the current thread to the thread that acquired the write lock, returning true
            setExclusiveOwnerThread(current);
            return true;
        }
Copy the code

Failed to get the lock and join the queue. See my previous article ReentrantLock

summary

If there is no write or read lock, the first thread to acquire the lock succeeds, regardless of whether the lock is written or read.

If you already have a read lock, obtaining the write lock will fail. Obtaining the read lock may or may not work.

If a write lock already exists, then the read lock or write lock is acquired, if the thread is the same (reentrant), then it succeeds; Otherwise fail.

5, read lock release

Without further ado, above

doReleaseShared()Wake up the next node after successful release

The tryReleaseShared() source code is implemented as follows

 protected final boolean tryReleaseShared(int unused) {
            Thread current = Thread.currentThread();
      // if the thread is the first to acquire the read lock
            if (firstReader == current) {
                if (firstReaderHoldCount == 1)
                    firstReader = null;
                else
                    firstReaderHoldCount--;
            }
     // The current thread's counter is -1
            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();
                // Release a read lock because 16 bits higher represents a read lock, so -65536
                int nextc = c - SHARED_UNIT;
                // The update returned successfully, if not, try
                if (compareAndSetState(c, nextc))
                    return nextc == 0; }}Copy the code

6, write lock release

TryReleasevi releases the write lock. After release, the blocked thread needs to be woken up if there are other threads in the queue.

Protected final Boolean tryRelease(int Releases) {// If no thread is holding the write lock but is still releasing it, throw an exception if (! isHeldExclusively()) throw new IllegalMonitorStateException(); int nextc = getState() - releases; Boolean Free = exclusiveCount(nexTC) == 0; If (free) setExclusiveOwnerThread(null); setExclusiveOwnerThread(null); setState(nextc); return free; }Copy the code

summary

From the above source can be seen:

1. If the write lock is currently occupied, it is considered to be released successfully only when the data of the write lock decreases to 0. Otherwise fail. As long as there is a write lock, no other thread can acquire either the read lock or the write lock except the thread that owns the write lock.

2. If the read lock is currently held, the release is considered successful only when the number of write locks is 0. Because once there is a write lock, no other thread should be able to acquire the read lock, except the thread that acquired the write lock.

My ability is limited, write not very good please see the officer do not tangle

My ability is limited, write not very good please see the officer do not tangle

My ability is limited, write not very good please see the officer do not tangle

Refer to the connection

Concurrent Programming – Read Lock source Code Analysis (Explain the lock degradation controversy)

Concurrency advanced (12) read and write lock

ReadWriteLock source code analysis