AQS
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable ...
AQS, AbstractQueuedSynchronizer (Abstract queue synchronizer), is an abstract class of other synchronizers
It inherits an AOS (AbstractOwnableSynchronizer), which is only used to save threads that hold locks
Some important attributes
Some properties and internal classes of AbstractQueuedSynchronizer are as follows:
//Queue header of synchronization queue private transient volatile Node head; //End of synchronization queue private transient volatile Node tail; //The meaning of status value is related to the specific implementation class private volatile int state; //Related to Condition, accurate wake-up can be realized public class ConditionObject{ //Queue header of condition queue private transient Node firstWaiter; //End of condition queue private transient Node lastWaiter; } //Each node of the queue corresponds to a thread static final class Node { // Represents shared mode and exclusive mode respectively static final Node SHARED = new Node(); static final Node EXCLUSIVE = null; // Indicates that the current node task has been cancelled static final int CANCELLED = 1; // Indicates that the next thread is waiting to wake up static final int SIGNAL = -1; // Indicates that the current node is a conditional node, static final int CONDITION = -2; // Indicates that releaseShared should be propagated to other nodes static final int PROPAGATE = -3; // Wait state, the initial value is 0, or the above value can be taken volatile int waitStatus; // Represents the previous node and the latter node respectively volatile Node prev; volatile Node next; //Thread object corresponding to Node volatile Thread thread; // Point to the next node corresponding to the special Condition object or SHARED node Node nextWaiter; }
Template method of AQS
AQS class defines several template methods to be overridden by subclasses. As you can see, AQS provides exclusive (exclusive) and non exclusive (shared) modes. You only need to rewrite the modes that need to be used.
//Try to acquire the lock object in exclusive and shared mode respectively protected boolean tryAcquire(int arg) { throw new UnsupportedOperationException(); } protected int tryAcquireShared(int arg) { throw new UnsupportedOperationException(); } //Attempt to release the lock object in exclusive and shared mode respectively protected boolean tryRelease(int arg) { throw new UnsupportedOperationException(); } protected boolean tryReleaseShared(int arg) { throw new UnsupportedOperationException(); } //Returns whether the current thread holds the lock in exclusive mode protected boolean isHeldExclusively() { throw new UnsupportedOperationException(); }
Several important methods in AQS
acquire lock
Start with acquire
// Attempt to exclusive lock object public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
tryAcquire(arg) is overridden by subclasses and will not be analyzed temporarily
Now analyze addWaiter(Node mode) and acquirequeueueueueed (final node, int ARG) respectively
// Add to waiting queue and return private Node addWaiter(Node mode) { Node node = new Node(Thread.currentThread(), mode); // If a waiting queue already exists, you can add it directly. Otherwise, you need to build a waiting queue first Node pred = tail; if (pred != null) { node.prev = pred; if (compareAndSetTail(pred, node)) { pred.next = node; return node; } } enq(node); return node; } // Initialize the waiting queue. An empty node will be formed at the head node, and then add a node node to the queue private Node enq(final Node node) { for (;;) { Node t = tail; if (t == null) { // Must initialize if (compareAndSetHead(new Node())) tail = head; } else { node.prev = t; if (compareAndSetTail(t, node)) { t.next = node; return t; } } } } // After joining the waiting queue, if the conditions are met, you can try to obtain the lock object again, or block if you can't obtain it and wait for wake-up final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; for (;;) { final Node p = node.predecessor(); // If it is the successor node of the head node, you can try to obtain the lock object again (in fact, it should be twice. See the explanation below) if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; // help GC failed = false; return interrupted; } //Judge whether it should be blocked, and if so, block if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); } }
Next, analyze shouldParkAfterFailedAcquire(Node pred, Node node), parkAndCheckInterrupt() and cancelAcquire(Node node)
For shouldParkAfterFailedAcquire(Node pred, Node node) method, when each node is called for the first time, the waitStatus of the front node is 0. Therefore, false will be returned for the first time, and if (P = = head & & tryacquire (ARG)) in acquirequeueueueueueuented (final node, int ARG) method will run twice before blocking.
// To return whether the current thread should be blocked (determined by the waitStatus of the preceding Node), you can see the explanation of the Node attribute at the beginning of this article private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { int ws = pred.waitStatus; // Returns true if the waitStatus of the front node is SIGNAL(-1) if (ws == Node.SIGNAL) return true; // If > 0 (in fact, it is 1, which means cancelled), the cancelled node is skipped if (ws > 0) { do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; } // Block the thread and return whether to interrupt private final boolean parkAndCheckInterrupt() { LockSupport.park(this); return Thread.interrupted(); } // Cancel node task private void cancelAcquire(Node node) { if (node == null) return; // The thread in the canceled node is null node.thread = null; Node pred = node.prev; // Find a non cancelled node forward and record it as PRED. The precursor pointer of the modified node points to pred while (pred.waitStatus > 0) node.prev = pred = pred.prev; Node predNext = pred.next; // Change state node.waitStatus = Node.CANCELLED; // If node is the tail node, set pred as the tail node if (node == tail && compareAndSetTail(node, pred)) { compareAndSetNext(pred, predNext, null); } else { int ws; // If pred is not a header node, set its waitStatus to SIGNAL. If the thread of pred is not empty, proceed to the next step if (pred != head && ((ws = pred.waitStatus) == Node.SIGNAL || (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) && pred.thread != null) { Node next = node.next; // If the post node of node is not empty and has not been cancelled, set the post node of pred as the next node if (next != null && next.waitStatus <= 0) compareAndSetNext(pred, predNext, next); } else { // If pred is the head node, wake up the successor node unparkSuccessor(node); } node.next = node; // help GC } }
release lock
Now, analyze the process of releasing the lock
Start with release
public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); // If the header node is empty or the waitStatus of the header node is 0, skip the unparksuccess (H) method return true; } return false; }
tryRelease(arg) is overridden by subclasses and will not be analyzed temporarily
We further analyze the unparkwinner (node node) method
private void unparkSuccessor(Node node) { int ws = node.waitStatus; // If the current node is not cancelled, set waitStatus to 0 if (ws < 0) compareAndSetWaitStatus(node, ws, 0); Node s = node.next; // If the successor node is empty or cancelled, start from the end of the queue to find a non node node that has not been cancelled if (s == null || s.waitStatus > 0) { s = null; for (Node t = tail; t != null && t != node; t = t.prev) if (t.waitStatus <= 0) s = t; } if (s != null) LockSupport.unpark(s.thread); }
Therefore, after the release method releases the lock, if there is a next node and the next node is not cancelled, the thread corresponding to the next node will be awakened
If there is a next node, but the next node is cancelled, start from the end of the queue to find a node that has not been cancelled and wake up the corresponding thread
Specific applications include ReentrantLock, ReentrantReadWriteLock, CountDownLatch, CyclicBarrier, Semaphore, etc.
When learning the above categories, we have two concerns
① Meaning of state field in AQS
② Subclass overrides template methods in AQS
ReentrantLock
ReentrantLock is a reentrant lock. It rewrites three template methods and realizes two exclusive locks: fair and unfair.
FairSync and NonfairSync both inherit Sync, and Sync inherits AQS
Reentrant bottom layer
① The state field indicates the number of layers occupied by the current thread lock (reentrant lock and reentrant key)
This value is + 1 for each layer; The value is - 1 for each layer
When it is 0, it means that there is no thread occupation
See below for codes
Fair and unfair
View key codes directly
// Attempt to acquire locks fairly protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { // Here we will judge whether there is a waiting queue, which is the embodiment of fairness if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { // This method is in AOS (briefly introduced at the beginning of this article) and is used to set the thread object in it setExclusiveOwnerThread(current); return true; } } // Reentrant key else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; } // Unfair attempt to acquire lock final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { // Note the difference here if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }
Other applications will not be considered for the time being.