preface

ReentrantReadWriteLock is one of the most complex lock implementation classes in the world. In this article, you will learn how to treadwritelock read and write locks. Before you read this article, you will need to understand some basic concepts such as AQS, fair and unfair locks. After knowing these knowledge points, you can understand ReentrantReadWriteLock more easily because ReentrantReadWriteLock is built on these points.

ReentrantReadWriteLock Total logic

AQS maintains this blocking thread queue. When the blocking thread queue is empty, the reader thread can acquire the read lock at the same time. Before all the reader threads release the read lock, if any writer thread wants to acquire the write lock, it will block the thread and join the blocking queue. Subsequent read-write threads are added to the blocking queue by tailgating all reader threads before the first writer thread releases the lock. After all the reader threads prior to the first writer thread release the lock, the thread in the queue will wake up to try to lock.

ReentrantReadWriteLock Basic internal unit

Sync inherits AQS, and the core business logic of read-write lock is all in AQS. No matter fair lock or unfair lock, read lock or write lock, they just call the methods in Sync to achieve fair and unfair competition, lock and unlock business logic. The write lock is unlocked by calling Sync#tryAcquire() and Sync#tryRelease(). Read lock Sync#tryAcquireShared() to lock, Sync#tryReleaseShared() to lock. The essence of these methods is to change the lock state by changing the value of AQS#state.

/ / read lock
private final ReentrantReadWriteLock.ReadLock readerLock;
/ / write locks
private final ReentrantReadWriteLock.WriteLock writerLock;
// Abstract static inner class, inherit AQS, read and write lock core business logic is in Sync class
final Sync sync;
// Not fair lock, inherit Sync
static final class NonfairSync extends Sync {
    private static final long serialVersionUID = -8159625535654395037L;
    final boolean writerShouldBlock(a) {
        return false;
    }
    final boolean readerShouldBlock(a) {
        returnapparentlyFirstQueuedIsExclusive(); }}// Fair lock, inherit Sync
static final class FairSync extends Sync {
    private static final long serialVersionUID = -2274990926593161451L;
    final boolean writerShouldBlock(a) {
        return hasQueuedPredecessors();
    }
    final boolean readerShouldBlock(a) {
        returnhasQueuedPredecessors(); }}/ / read lock
public static class ReadLock implements Lock.java.io.Serializable {
    private static final long serialVersionUID = -5992448646407690164L;
    private final Sync sync;
    // constructor
    protected ReadLock(ReentrantReadWriteLock lock) {
        sync = lock.sync;
    }
    // Read lock lock method
    public void lock(a) {
        sync.acquireShared(1);
    }
    // Read lock unlock method
    public void unlock(a) {
        sync.releaseShared(1); }}public static class WriteLock implements Lock.java.io.Serializable {
    private static final long serialVersionUID = -4992448646407690164L;
    private final Sync sync;
    // constructor
    protected WriteLock(ReentrantReadWriteLock lock) {
        sync = lock.sync;
    }
    // Write lock add lock method
    public void lock(a) {
        sync.acquire(1);
    }
    // Write lock unlock method
    public void unlock(a) {
        sync.release(1); }}Copy the code

Let’s take a look at the basic components of the abstract inner class Sync. Any concurrent class in Java is implemented by defining the state code of type INT in AQS. ReentrantReadWriteLock divides the state code into high 16 bits and low 16 bits. The high 16 bits of the state code are used to store the number of concurrent read threads (the reader-thread reentrant count is also added), that is, the high 16 bits of the state code = (the reentrant count of thread 1 + the reentrant count of thread 2 +… + reentrant times for thread n). The lower 16 bits of the state code store the number of reentrant counts for a single writer thread. (Write locks are mutually exclusive and only one thread can acquire them.)

abstract static class Sync extends AbstractQueuedSynchronizer {
    static final int SHARED_SHIFT   = 16;
    // For high 16 + 1 operations (high 16 bits for the number of concurrent reads)
    static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
    // The maximum number of concurrent read threads (the reader-thread reentrant count is also added) = the maximum number of reentrants for a single writer-thread = 65535
    static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
    // The 16-bit binary 1 is used to count the number of times a single write process is flushed at the lower 16 bits
    static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;

    // Returns the number of high 16 bit concurrent read threads
    static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
    // Returns the reentrant count of a single write process in the lower 16 bits
    static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
    // This saves the thread ID and the corresponding reentrant count
    static final class HoldCounter {
        int count = 0;
        final long tid = getThreadId(Thread.currentThread());
    }
    // Inherits from ThreadLocal to store the reentrant count of all readers except the first reader thread
    static final class ThreadLocalHoldCounter
        extends ThreadLocal<HoldCounter> {
        public HoldCounter initialValue(a) {
            return newHoldCounter(); }}// This is used to store the reentrant count of all reader threads except the first reader thread
    private transient ThreadLocalHoldCounter readHolds;
    // The id and reentrant count of the last read-thread
    private transient HoldCounter cachedHoldCounter;
    
    // Error for the first read-thread object
    private transient Thread firstReader = null;
    // Used to store the reentrant count of the first reader thread
    private transient int firstReaderHoldCount;

    Sync() {
        readHolds = new ThreadLocalHoldCounter();
        setState(getState()); // ensures visibility of readHolds
    }

    abstract boolean readerShouldBlock(a);

    abstract boolean writerShouldBlock(a);
    // Write thread unlock method
    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;
    }

    // Write thread lock method
    protected final boolean tryAcquire(int acquires) {

        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)
            if (w == 0|| current ! = getExclusiveOwnerThread())return false;
            if (w + exclusiveCount(acquires) > MAX_COUNT)
                throw new Error("Maximum lock count exceeded");
            // Reentrant acquire
            setState(c + acquires);
            return true;
        }
        if(writerShouldBlock() || ! compareAndSetState(c, c + acquires))return false;
        setExclusiveOwnerThread(current);
        return true;
    }
    // Unlock the reader thread
    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;
            if (compareAndSetState(c, nextc))
                return nextc == 0; }}// Read thread lock method
    protected final int tryAcquireShared(int unused) {
        Thread current = Thread.currentThread();
        int c = getState();
        if(exclusiveCount(c) ! =0&& getExclusiveOwnerThread() ! = current)return -1;
        int r = sharedCount(c);
        if(! readerShouldBlock() && r < MAX_COUNT && compareAndSetState(c, c + SHARED_UNIT)) {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);
    }

    final int fullTryAcquireShared(Thread current) {
        HoldCounter rh = null;
        for (;;) {
            int c = getState();
            if(exclusiveCount(c) ! =0) {
                if(getExclusiveOwnerThread() ! = current)return -1;
            } else if (readerShouldBlock()) {
                if (firstReader == current) {
                } 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 (sharedCount(c) == MAX_COUNT)
                throw new Error("Maximum lock count exceeded");
            if (compareAndSetState(c, c + SHARED_UNIT)) {
                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;
                }
                return 1; }}}}Copy the code

Initialization process

ReentrantReadWriteLock initialization process

public class LearnReadWriteLock {
    public static void main(String[] args){
        ReentrantReadWriteLock readWriteLock = newReentrantReadWriteLock(); }}Copy the code
/*ReentrantReadWriteLock#ReentrantReadWriteLock()*/
public ReentrantReadWriteLock(a) {
        this(false);
    }
/*ReentrantReadWriteLock#ReentrantReadWriteLock(boolean fair)*/
public ReentrantReadWriteLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
        readerLock = new ReadLock(this);
        writerLock = new WriteLock(this);
    }
/*ReentrantReadWriteLock$ReadLock#ReadLock(ReentrantReadWriteLock lock)*/
protected ReadLock(ReentrantReadWriteLock lock) {
            sync = lock.sync;
        }
/*ReentrantReadWriteLock$WriteLock#WriteLock(ReentrantReadWriteLock lock)*/
protected WriteLock(ReentrantReadWriteLock lock) {
            sync = lock.sync;
        }
Copy the code

As we can see, the default initialization is an unfair lock. We look at the initialization logic of the read/write lock. They all pass in the current ReentrantReadWriteLock pair lock and set the sync parameter to lock.sync. The business logic of the read-write lock is implemented by calling the methods in sync, which is the key class to implement the read-write lock.

Read lock lock process

public class LearnReadWriteLock {
    public static void main(String[] args){
        ReentrantReadWriteLock readWriteLock = newReentrantReadWriteLock(); Lock readLock = readWriteLock.readLock(); readLock.lock(); }}Copy the code
/*ReentrantReadWriteLock$ReadLock#lock()*/
public void lock(a) {
    sync.acquireShared(1);
}
/*AbstractQueuedSynchronizer#acquireShared()*/
public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}
/*ReentrantReadWriteLock$Sync#tryAcquireShared()*/
protected final int tryAcquireShared(int unused) {
    Thread current = Thread.currentThread();
    // Get the state code in AQS
    int c = getState();
    // Returns -1 if the reentrant count of the lower 16-bit exclusive writer thread is not zero and the exclusive thread is not equal to the current thread.
    if(exclusiveCount(c) ! =0&& getExclusiveOwnerThread() ! = current)return -1;
    // Get the sum of the reentrant counts of all the reader threads with the highest 16 bits
    int r = sharedCount(c);
    // The current thread is allowed to acquire the read lock if the blocking queue is empty or if the sum of the reentrant counts of all the read-threads in the 16-bit high block queue is less than 65535
    if(! readerShouldBlock() && r < MAX_COUNT && compareAndSetState(c, c + SHARED_UNIT)) {// If it is the first thread to acquire the read lock, the first thread to acquire the read lock is recorded with firstReader and the first thread to acquire the read lock is reentrant firstReaderHoldCount is 1.
        if (r == 0) {
            firstReader = current;
            firstReaderHoldCount = 1;
            // If the current thread is the first thread to acquire the lock
        } else if (firstReader == current) {
            // Increments the reentrant count of the thread acquiring the lock for the first time by 1
            firstReaderHoldCount++;
        } else {
            // If not the first thread to acquire the read lock
            // The thread that acquired the lock last time (id of the thread that acquired the lock last time and reentrant count)
            HoldCounter rh = cachedHoldCounter;
            / / if you don't have an access to read lock thread information (the first thread fetching the read lock is not) | | a acquire read lock on the thread id is not equal to the current thread id
            if (rh == null|| rh.tid ! = getThreadId(current))TreadLocal Gets information about the current thread (reentry information). Set the information about the thread that obtained the read lock last time to the information about the current thread that obtained the read lock.
                cachedHoldCounter = rh = readHolds.get();
            else if (rh.count == 0)
                readHolds.set(rh);
            // The reentrant count of the current thread increases by 1
            rh.count++;
        }
        return 1;
    }
    return fullTryAcquireShared(current);
}
Copy the code

NonfairSync#readerShouldBlock() : a key step in the process of a thread acquiring a read lock is to determine whether the rival waiting thread blocking the queue is requesting the read lock or writing the lock. If the rival waiting thread requests a read lock, the current thread does not need to join the blocking queue and can compete unfairly for the read lock. If a write lock is acquired by a rival thread, the current thread joins the blocking queue and suspends.

/*ReentrantReadWriteLock$NonfairSync#readerShouldBlock()*/
final boolean readerShouldBlock(a) {
    return apparentlyFirstQueuedIsExclusive();
}
/*AbstractQueuedSynchronizer#apparentlyFirstQueuedIsExclusive()*/
final boolean apparentlyFirstQueuedIsExclusive(a) {
        Node h, s;
        return(h = head) ! =null&& (s = h.next) ! =null&&! s.isShared() && s.thread ! =null;
    }
/*AbstractQueuedSynchronizer#isShared()*/
final boolean isShared(a) {
    return nextWaiter == SHARED;
}
Copy the code

Write lock lock process

public class LearnReadWriteLock {
    public static void main(String[] args){
        ReentrantReadWriteLock readWriteLock = newReentrantReadWriteLock(); Lock writeLock = readWriteLock.writeLock(); writeLock.lock(); }}Copy the code
/*ReentrantReadWriteLock$WriteLock#lock()*/
public void lock(a) {
    sync.acquire(1);
}
/*AbstractQueuedSynchronizer#acquire()*/
public final void acquire(int arg) {
    if(! tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }/*ReentrantReadWriteLock$Sync#tryAcquire()*/
protected final boolean tryAcquire(int acquires) {
    Thread current = Thread.currentThread();
    // Get the state code in AQS
    int c = getState();
    // Get the reentrant count of the lower 16-bit exclusive thread
    int w = exclusiveCount(c);
    if(c ! =0) {
        / / if the exclusive thread number of reentrant w to 0 (status code c is not 0, exclusive thread number of reentrant w to 0 current has read lock haven't released, can't write lock) | | monopoly thread number of reentrant w but non zero for the current thread is not to get exclusive write locks have to thread.
        if (w == 0|| current ! = getExclusiveOwnerThread())// Failed to lock
            return false;
        // If the number of reentrants of the exclusive thread is greater than the maximum number of reentrants, an exception will be thrown
        if (w + exclusiveCount(acquires) > MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        // The reentrant count of the exclusive thread is increased by 1. CAS is not required because the exclusive thread is reentrant (no concurrency exists).
        setState(c + acquires);
        // Lock succeeded
        return true;
    }
    // If the status code c=0, no thread is currently acquiring the lock. WriterShouldBlock () returns false and uses CAS to increase the reentrant count of the writer-exclusive thread by 1
    if(writerShouldBlock() || ! compareAndSetState(c, c + acquires))return false;
    // Set the exclusive thread to the current thread
    setExclusiveOwnerThread(current);
    return true;
}
/*ReentrantReadWriteLock$NonfairSync#writerShouldBlock*/
final boolean writerShouldBlock(a) {
    return false; // writers can always barge
}

Copy the code

Write lock lock process is relatively simple, direct comments can understand, here is not repeated.

Read lock blocking process

When a thread acquires a read lock, it can block in two ways:

  • A thread has acquired the write lock and has not released the write lock.
  • A thread has acquired the read lock without releasing the read lock and the first blocking thread blocking the queue requests the write lock.

The first case is easy to understand. When a thread has already acquired a write lock, any other thread attempting to acquire the lock will fail and join the blocking queue. Let’s look at the second case.

public class LearnReadWriteLock {
    public static void main(String[] args){
        ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
        Lock readLock = readWriteLock.readLock();
        Lock writeLock = readWriteLock.writeLock();
        Thread 1 acquires the read lock
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run(a) { readLock.lock(); }},"Thread1-tryRead");
        thread1.start();
        Thread 2 acquires the write lock
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run(a) { writeLock.lock(); }},"Thread2-tryWrite");
        thread2.start();
        Thread 3 acquires the read lock
        Thread thread3 = new Thread(new Runnable() {
            @Override
            public void run(a) { readLock.lock(); }},"Thread3-tryRead"); thread3.start(); }}Copy the code

Joins the blocking queue. Head refers to the head Node of the blocking queue (the head Node does not contain information about the blocking thread, but is used to point to the Node corresponding to the first blocking thread of the blocking queue), head refers to the Node of the queue, and tail refers to the last Node of the queue.

AcquireQueued (addWaiter(Node.exclusive), arG) if thread fails to request a write lock. Node.EXCLUSIVE is empty, indicating that the currently blocking thread is requesting a write lock.

/*ReentrantReadWriteLock$WriteLock#lock()*/
public void lock(a) {
    sync.acquire(1);
}
/*AbstractQueuedSynchronizer#acquire()*/
public final void acquire(int arg) {
    if(! tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }/*AbstractQueuedSynchronizer#acquire()*/
private Node addWaiter(Node mode) {// Node.EXCLUSIVE(Node EXCLUSIVE = null);
    // Create a Node for the current blocked thread to store basic information about the blocked thread
    Node node = new Node(Thread.currentThread(), mode);
    Node pred = tail;
    if(pred ! =null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            returnnode; }}// If the tail node is empty, create the first and last nodes and use the tail method to insert the current node after the tail node
    enq(node);
    return node;
}
/*AbstractQueuedSynchronizer$Node#Node(Thread thread, Node mode)*/
Node(Thread thread, Node mode) {
    // nextWaiter = null indicates that the current blocking thread is requesting a write lock
    this.nextWaiter = mode;
    // Used for thread to store blocking thread objects
    this.thread = thread;
}
/*AbstractQueuedSynchronizer$Node#enq(final Node node)*/
private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        if (t == null) { // Must initialize
            // Use CAS to make head point to the new head node
            if (compareAndSetHead(new Node()))
                // Since the current blocking queue has only one head node, the head node is the tail node
                tail = head;
        } else {
            // The first node of the new blocking node is the tail node
            node.prev = t;
            // Use CAS to point the tail pointer to the current node
            if (compareAndSetTail(t, node)) {
                // The last node of the previous tail node points to the new tail node
                t.next = node;
                returnt; }}}}Copy the code

After the addWaiter(Node.exclusive) addWaiter(Node.exclusive) is executed, the Node is inserted into the blocking queue, as shown in the following figure. The head points to the head Node of the blocking queue, the head points to the Node of the queue. Tail Points to the tail node of the queue. After the blocking node corresponding to thread 2 is inserted into the blocking queue, the blocking queue is as follows:

Thread 2 is not suspended after its corresponding blocking node is inserted into the blocking queue. AcquireQueued (node, arG) acquireQueued(final node, int ARg)

/*AbstractQueuedSynchronizer#acquireQueued(final Node node, int arg)*/
final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                // Get the previous node of the current node
                final Node p = node.predecessor();
                // If the previous node is the head node, try again to acquire the write lock
                if (p == head && tryAcquire(arg)) {
                    // If the write lock is acquired successfully, the blocking node corresponding to the current thread is removed from the blocking queue
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                / / shouldParkAfterFailedAcquire (p, node) of the previous node of the current node waitStatus set to 1, and again in the for loop, if still can't get to write lock is suspends the current thread
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true; }}finally {
            if(failed) cancelAcquire(node); }}Copy the code

AcquireQueued (Final Node Node, int arg) does a for loop twice to let thread 2 attempt to acquire the write lock. This condition is obviously met, so thread 2 tries twice to acquire the write lock, but again fails, and ends up calling parkAndCheckInterrupt() to suspend. Blocking queue diagram after thread 2 is suspended is shown below.

Finally, let’s look at the process of thread 3 acquiring the read lock. The only way to determine whether thread 3 needs to block is if the first blocking thread in the queue requests a read lock. If it requests a read lock, thread 3 tries to acquire the read lock (unfair). If a write lock is requested, thread 3 needs to block.

/*ReentrantReadWriteLock$ReadLock#lock()*/
public void lock(a) {
    sync.acquireShared(1);
}
/*AbstractQueuedSynchronizer#acquireShared()*/
public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}
/*ReentrantReadWriteLock$Sync#tryAcquireShared()*/
protected final int tryAcquireShared(int unused) {
    Thread current = Thread.currentThread();
    // Get the state code in AQS
    int c = getState();
    // Returns -1 if the reentrant count of the lower 16-bit exclusive writer thread is not zero and the exclusive thread is not equal to the current thread.
    if(exclusiveCount(c) ! =0&& getExclusiveOwnerThread() ! = current)return -1;
    // Get the sum of the reentrant counts of all the reader threads with the highest 16 bits
    int r = sharedCount(c);
    // The current thread is allowed to acquire the read lock if the blocking queue is empty or if the sum of the reentrant counts of all the read-threads in the 16-bit high block queue is less than 65535
    if(! readerShouldBlock() && r < MAX_COUNT && compareAndSetState(c, c + SHARED_UNIT)) {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);
}
/*ReentrantReadWriteLock$NonfairSync#readerShouldBlock()*/
final boolean readerShouldBlock(a) {
    return apparentlyFirstQueuedIsExclusive();
}
/*AbstractQueuedSynchronizer#apparentlyFirstQueuedIsExclusive()*/
final boolean apparentlyFirstQueuedIsExclusive(a) {
    Node h, s;
    return(h = head) ! =null&& (s = h.next) ! =null&&! s.isShared() && s.thread ! =null;
}
/*AbstractQueuedSynchronizer#isShared()*/
final boolean isShared(a) {
    return nextWaiter == SHARED;
}
Copy the code

Obviously, thread 2, the first blocking thread in the current blocking queue, attempts to acquire a write lock, and then runs the blocking method doAcquireShared(arg); DoAcquireShared (ARG) inserts thread 3’s blocking node into the blocking queue, tries twice to acquire the read lock, and suspends the current thread if it still can’t. Obviously thread 3 will still fail to acquire the read lock.

/*AbstractQueuedSynchronizer#doAcquireShared()*/
private void doAcquireShared(int arg) {
    // Insert the blocking node corresponding to the current thread into the blocking queue
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            // If the previous node of the blocking node corresponding to the current thread is the head node, directly try to acquire the read lock
            if (p == head) {
                // Succeeded in obtaining the read lock
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    if (interrupted)
                        selfInterrupt();
                    failed = false;
                    return; }}/ / shouldParkAfterFailedAcquire (p, node) of the previous node of the current node waitStatus set to 1, and again in the for loop, if still can't get to read lock may suspend the current thread
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true; }}finally {
        if(failed) cancelAcquire(node); }}/*AbstractQueuedSynchronizer#acquire()*/
private Node addWaiter(Node mode) {// Node.SHARED(Node SHARED = new Node();)
    // Create a Node for the current blocked thread to store basic information about the blocked thread
    Node node = new Node(Thread.currentThread(), mode);
    Node pred = tail;
    // Insert thread 3's blocking node at the end
    if(pred ! =null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    enq(node);
    return node;
}
Copy the code

Block the queue after thread 3 is suspended as shown below.

This is all about ReentrantReadWriteLock obtaining read/write locks. The mechanism of unlocking is not described here. A few points to note:

  • If a thread has already acquired the write lock, any other thread attempting to acquire the read/write lock will fail.
  • If a thread has obtained the read lock, when another thread attempts to obtain the read lock, it needs to determine the type of the lock that the first blocking thread attempts to obtain. If the lock is a read lock, it can compete for the read lock unfairly. If it is a write lock, it joins the blocking queue and is suspended after two failed attempts to acquire the read lock.