principle
The full name is AbstractQueuedSynchronizer. When a thread goes to get resources, it will judge whether there is a lock according to the state value state. If there is a lock, it will be added to the linked list. The thread in the linked list will determine whether the resources have been released by spinning. If it is released, it will get resources.
AQS structure
- volatile Node head: blocked head node
- volatile Node tail: the tail node of blocking. The new blocking node is added to the end.
- volatile int state: lock state, 0 indicates unoccupied, greater than or equal to 1 indicates occupied
- Thread exclusiveOwnerThread: current thread occupying lock
Node: node information of bidirectional linked list
- Node EXCLUSIVE: exclusive mode
- Volatile thread: current thread
- volatile Node prev: front node
- volatile Node next: post node
- volatile int waitStatus: status field, - 1: waiting to wake up, greater than 0, cancelled
Source code analysis
Take unfair lock as an example.
Lock up
lock
Get lock
final void lock() { if (compareAndSetState(0, 1))//Set to 1 if status is 0 setExclusiveOwnerThread(Thread.currentThread());//Set occupation lock as current thread else acquire(1);//Resource occupied }
acquire
After the lock is occupied, try to acquire it again. If the lock cannot be acquired, enter the blocking queue.
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
tryAcquire:
The non fairtryacquire method of Sync is called.
final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread();//Get current thread int c = getState();//Get current status if (c == 0) { if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) {//For the current thread, reentry int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }
addWaiter
Encapsulate the thread as a node and join the queue
private Node addWaiter(Node mode) { Node node = new Node(Thread.currentThread(), mode); // Try the fast path of enq; backup to full enq on failure Node pred = tail;//Get tail node if (pred != null) { node.prev = pred;//The front node of the current node is the tail node obtained. if (compareAndSetTail(pred, node)) {//Set current node as tail node pred.next = node;//If cas operation is successful, set bidirectional linked list return node; } } enq(node); return node; }
enq
Add to tail node by spinning
private Node enq(final Node node) { for (;;) { Node t = tail;//Get tail node if (t == null) { // If the tail node is empty, initialize the head node and the tail node with the same address. if (compareAndSetHead(new Node())) tail = head; } else { node.prev = t; if (compareAndSetTail(t, node)) {//Join tail node t.next = node; return t; } } } }
acquireQueued
final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; for (;;) { final Node p = node.predecessor();//Get front node if (p == head && tryAcquire(arg)) {//If the front node is head, and the lock is acquired setHead(node);//Set the current node above the head p.next = null; // help GC failed = false; return interrupted; } //If it's not the team leader, or if it hasn't robbed other threads if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())//If the team header is awake, suspend it with parkAndCheckInterrupt interrupted = true; } } finally { if (failed) cancelAcquire(node); } }
shouldParkAfterFailedAcquire
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { int ws = pred.waitStatus; if (ws == Node.SIGNAL) /* * This node has already set status asking a release * to signal it, so it can safely park. */ return true;//Wake up, return true if (ws > 0) { /* * Predecessor was cancelled. Skip over predecessors and * indicate retry. */ do { node.prev = pred = pred.prev;//If it is cancelled, the cancelled predecessor node replaces the predecessor node of the current node } while (pred.waitStatus > 0); pred.next = node;//Two way linked list } else { /* * waitStatus must be 0 or PROPAGATE. Indicate that we * need a signal, but don't park yet. Caller will need to * retry to make sure it cannot acquire before parking. */ compareAndSetWaitStatus(pred, ws, Node.SIGNAL);//Pre node status set to wake up } return false; }
awaken
unlock
public void unlock() { sync.release(1); }
release
public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h);//Wake up head node return true; } return false; }
tryRelease
protected final boolean tryRelease(int releases) { int c = getState() - releases;//Possible reentry if (Thread.currentThread() != getExclusiveOwnerThread())//Throw exception for non current thread throw new IllegalMonitorStateException(); boolean free = false; if (c == 0) {//cas is not used because only the current display is locked free = true; setExclusiveOwnerThread(null); } setState(c); return free; }
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) compareAndSetWaitStatus(node, ws, 0);//If the current waitstatus of the header node is less than 0, change it to 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; //Wake up the next node. If the first node is empty, traverse from the tail to get the node whose waitStatus is less than 0. 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);//awaken }