ReentrantLock class hierarchy
AbstractQueuedSynchronizer referred to as “AQS
AbstractQueuedSynchronizer internal use CLH lock queue to concurrent execution into serial execution. The entire queue is a bidirectional linked list. Each node in the CLH lock queue holds references to the previous node and the next node, the thread corresponding to the current node, and a state. This state is used to indicate whether the thread should block. When the previous node of the node is released, the current node is awakened and becomes the head. New nodes are placed at the end of the queue.
The lock method of an unfair lock
When initializing a ReentrantLock, if we do not pass the argument whether it is fair, we default to using an unfair lock, NonfairSync. This method first attempts to preempt the lock with the CAS(compareAndSetState) operation. If successful, the current thread is set on the lock, indicating a successful preemption. If that fails, the Acquire template method is called and waits for preemption.
static final class NonfairSync extends Sync { final void lock() { if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); } protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); }}Copy the code
Calls to acquire (1) is actually used AbstractQueuedSynchronizer acquire method, it is a set of lock preemption template, the overall principle is to try to get first lock, without success, in the CLH queue increase a node, the current thread said waiting for preemption. Then the CLH queue enters preemption mode. When entering, the lock is acquired once. If not, the current thread is suspended by calling locksupport. park. So when does the current thread wake up? When the thread that holds the lock calls UNLOCK, it wakes up the thread on the next node on the head of the CLH queue, using the locksupport.unpark method. The internal implementation is as follows:
public final void acquire(int arg) { if (! tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }Copy the code
NonfairTryAcquire () : nonfairTryAcquire ();
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
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;
}
Copy the code
-> return false; Then start the acquireQueued process by adding the thread to the tail node of the queue (CLH queue) using the addWaiter method
Private Node addWaiter(Node mode) {// initialize a Node that holds the currentThread. // If the CLH queue is not empty, insert a Node directly at the end of the queue. if (pred ! = null) { node.prev = pred; if (compareAndSetTail(pred, node)) { pred.next = node; return node; }} // When CLH queue is empty, call enq to initialize queue ENq (node); return node; } private Node enq(final Node node) { for (;;) { Node t = tail; If (compareAndSetHead(new Node())) tail = head; } else {// Consider concurrent initialization of node.prev = t; if (compareAndSetTail(t, node)) { t.next = node; return t; }}}}Copy the code
In the acquireQueued method, if the last node of the current node is the head node and tryAcquire has acquired the lock, prove that the previous node (the head node) has released the lock and wake up the current thread, then the current node becomes the head node and the old assignment =null waits for GC to drop.
final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; for (;;) { final Node p = node.predecessor(); if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; // help GC failed = false; return interrupted; } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) cancelAcquire(node); }}Copy the code
If the for loop did not get the lock, then into the shouldParkAfterFailedAcquire method,
1. If the previous node is in SIGNAL(-1) state, the current thread needs to be suspended based on the status shown in the following figure. Return 2. CANCELLED(s), which should be removed. 3. If it is in any other state, the thread attempts to set it to SIGNAL state and return that it does not need to suspend, thus performing the second preemption
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
//
return true;
if (ws > 0) {
//
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
//
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
Copy the code
When you enter the suspend phase, look again at the parkAndCheckInterrupt method, which calls park to suspend directly
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
Copy the code
Unlock method for an unfair lock
lock.unlock(); Public void unlock() {sync.release(1); } what is sync?? Is this the abstract static class Sync extends AbstractQueuedSynchronizerCopy the code
Can see it is actually called AbstractQueuedSynchronizer the release method (AQS). 1. Try tryRelease, which removes the lock’s exclusive thread and then decreases the state by one. This is done in consideration of the fact that the reentrant lock may itself occupy the lock multiple times. Only when the state reaches zero will the lock be completely released. If c==0, then the CHL queue head node is set to 0 and tryRelease succeeds. Determine the next node, if the state is not cancelled, call the unparksucceeded to wake the next node to see the concrete implementation:
public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; if (h ! = null && h.waitStatus ! = 0) unparkSuccessor(h); return true; } return false; }Copy the code
The tryRelease method of the ReentrantLock implementation:
protected final boolean tryRelease(int releases) { int c = getState() - releases; if (Thread.currentThread() ! = getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; if (c == 0) { free = true; setExclusiveOwnerThread(null); } setState(c); return free; }Copy the code
UnparkSuccessor method:
private void unparkSuccessor(Node node) { /* * If status is negative (i.e., possibly needing signal) try * to clear in anticipation of signalling. It is OK if this * fails or if status is changed by waiting thread. */ int ws = node.waitStatus; If (ws < 0) =0 compareAndSetWaitStatus(node, ws, 0); /* * Thread to unpark is held in successor, which is normally * just the next node. But if cancelled or apparently null, * traverse backwards from tail to find the actual * non-cancelled successor. */ Node s = node.next; 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) // Wake up locksupport. unpark(s.read); }Copy the code