introduce

A shared lock corresponds to an exclusive lock. As its name implies, a shared lock means that multiple threads can simultaneously hold the lock to process their own services. A common read lock is ReentrantReadWriteLock. In this article, we will first understand the synchronization queue of AQS how to handle the shared lock

Obtaining the shared lock

Let’s go straight to the source

Public final void acquireShared(int arg) {if (tryAcquireShared(arg) < 0) Enter the queue to try to get doAcquireShared(ARG); }Copy the code

The tryAcquireShared method is implemented by the subimplementation class, so we don’t need to pay too much attention to it. If the method returns a value greater than 0, the lock is successfully acquired, and if it fails, the lock is successfully acquired

private void doAcquireShared(int arg) { final Node node = addWaiter(Node.SHARED); // Create nextWaiter SHARED Node Boolean failed = true; try { boolean interrupted = false; for (;;) { final Node p = node.predecessor(); If (p == head) {int r = tryAcquireShared(arg); Propagate(node, r); propagate (node, r); // reset the header, wake up the back node if the back node is also a shared lock. // help GC if (interrupted) selfInterrupt(); failed = false; return; } } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); }}Copy the code
private void setHeadAndPropagate(Node node, int propagate) { Node h = head; setHead(node); / / set the current Node into a head Node if (the propagate > 0 | | h = = null | | h.w. aitStatus < 0 | | (h = head) = = null | | h.w. aitStatus < 0) {Node s = node.next; If (s = = null | | s.i sShared ()) / / if the rear node is not null, and nextWaiter as SHARED mode, to awaken doReleaseShared rear node (); }}Copy the code

By reading the code above, we can see that the logic is as follows:

  1. If the shared lock is obtained successfully, the method ends

  2. If the SHARED lock fails to be obtained, the current thread is encapsulated as Node, its nextWaiter is identified as SHARED mode, waitStatus is the default value 0, and appended to the end of the synchronization queue

  3. Get the front node of the current node. If the front node is not the head node, process it according to waitStatus of the front node

    3.1 waitStatus == SIGNAL of the front node indicates that the front node will wake up the back node after obtaining the lock. Therefore, it directly returns true to interrupt the current node

    3.2 If the front-node waitStatus > 0, that is, the status is cancelled, the node is removed from the queue, and the node is searched iteratively until waitStatus > 0 is the front-node of itself

    3.3 If waitStatus of the front node is different, set it to SIGNAL

    If the front node wakes up, it blocks the current thread. If not, the front node of the table wakes up and blocks itself

  4. If the current node is a head node and the shared lock is acquired successfully, it sets itself as a head node. If the post-node is also acquired the shared lock, it wakes up and returns the end

The shared lock is released

Let’s move on to the release of shared locks

Public Final Boolean releaseShared(int arg) {if (tryReleaseShared(arg)) {doReleaseShared(); Return true; } return false; }Copy the code

The tryReleaseShared method is implemented by the child implementation class, so let’s not worry too much about this either, except that if the method returns true, the shared lock is released successfully, and if it fails, the shared lock is released successfully

private void doReleaseShared() { for (;;) { Node h = head; if (h ! = null && h ! = tail) { int ws = h.waitStatus; if (ws == Node.SIGNAL) { if (! compareAndSetWaitStatus(h, Node.SIGNAL, 0)) continue; unparkSuccessor(h); } else if (ws == 0 &&! compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) continue; } if (h == head) break; }}Copy the code

The release logic of the shared lock is relatively simple. First, it is determined that if the shared lock can be released, the head node is acquired. If its waitStatus is SIGNAL, the later node is woken up. No further processing is required (if waitStatus of the current node is not SIGNAL, the subsequent node is not blocked and therefore does not need to be woken up)

At the end

Shared lock access and release is basically such, we come down with ReentrantReadWriteLock source code to deepen understanding

This article is written by reading the source code and based on my own understanding, if there are incorrect places welcome correction