We all know that AQS maintains a FIFO wait queue, and only when there is a resource competition will form a queue, so its process is exactly what? Let’s find out!
Let’s simulate a situation where t1 thread already holds the lock via CAS, state = 1, exclusiveOwnerThread = T1, and T2 wants to acquire the lock
The process of entrance
private Node addWaiter(Node mode) {
// Wrap the T2 thread as node
Node node = new Node(Thread.currentThread(), mode);
// Try to join a team. If you fail, enter enQ
// The first time you join a queue, you must fail because the queue has not been initialized
Node pred = tail;
// If there is a tail node, the entry condition
if(pred ! =null) {
node.prev = pred;
// If there is a tail element, set the current node to the tail node
if (compareAndSetTail(pred, node)) {
//pred <- node
pred.next = node;
return node;
}
}
//t2 enter queue
enq(node);
return node;
}
private Node enq(final Node node) {
// Loops twice, first to create the head node and second to append T2 to the end node
for (;;) {
Node t = tail;
// Initialize the queue with a new Thread=null node as the header
if (t == null) {
if (compareAndSetHead(new Node()))
tail = head;
} else {
//head <- t2
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
Copy the code
After the first loop, a Thread = null header is created, and both head and tail point to the header
After the second loop, we append T2 to the end node and point the tail pointer to T2, making the end node and T2 bidirectionally linked
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
// Get the precursor node of T2
final Node p = node.predecessor();
// If it is the head node, try to acquire the lock because t1 may have already released the lock
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
// If T2 fails to acquire the lock, it needs to block
// Attention!! When this method first enters, T2 will have the waitStatus CAS (0,-1) of the head node.
// and return false to block T2 in the next loop
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
Copy the code
For the first time, the waitStatus of the first node is changed from 0 (default) -> -1 (signal), indicating that the subsequent nodes need to be woken up after the node exits the queue
The second loop calls locksuppurt.park (), which actually blocks T2
At this point, the entry of T2 has been completed, which can be summarized as follows:
- Wrap the T2 thread as a node node
- Create a new head node where Thread = null and append T2 to the head node to establish a reference relationship
- Change the header’s waitStatus to signal, blocking T2
The team process
public final boolean release(int arg) {
/ / set the state = 0, setExclusiveOwnerThread (null)
if (tryRelease(arg)) {
Node h = head;
// There is a head node with waitStatus equal to -1
if(h ! =null&& h.waitStatus ! =0)
// Wake up the successor node
unparkSuccessor(h);
return true;
}
return false;
}
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
// Set the header's waitStatus to 0 to prevent another thread from waking up
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
// Cancel the execution
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;
}
/ / wake t2
if(s ! =null)
LockSupport.unpark(s.thread);
}
Copy the code
At this point, T2 is woken up and the acquireQueued method continues to execute, entering the loop
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
// Get the precursor node of T2
final Node p = node.predecessor();
// The attempt to acquire the lock succeeded because t1 has released the lock and the thread holding the lock has become T2
if (p == head && tryAcquire(arg)) {
// Change the reference relationship by setting T2 as the head node
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
.
} finally {
if (failed)
cancelAcquire(node);
}
}
Copy the code
At this point, t1 queue is completed, to sum up:
- Change the waitStatus of T1 to 0 to avoid multiple threads waking up at the same time
- After waking up T2, the attempt to acquire the lock succeeded, i.e
State = 1, exclusiveOwnerThread = t2
- Set T2 as the head node, head to T2, and Thread = null