CLH synchronization queue is a FIFO two-way queue. AQS relies on it to manage the synchronization status. If the current thread fails to obtain the synchronization status, AQS will construct the current thread's waiting status and other information into a Node and add it to the CLH synchronization queue. At the same time, it will block the current thread. When the synchronization status is released, it will wake up the first Node (fair lock), Make it try to get the synchronization status again.
In the CLH synchronization queue, a node represents a thread, which stores the thread reference, waitStatus, prev and next of the thread, which are defined as follows:
static final class Node { /** share */ static final Node SHARED = new Node(); /** monopoly */ static final Node EXCLUSIVE = null; /** * Because of timeout or interruption, the node will be set to the cancelled state. If the cancelled node does not participate in the competition, it will remain in the cancelled state and will not change to other states; */ static final int CANCELLED = 1; /** * The thread of the successor node is in the waiting state, and if the thread of the current node releases the synchronization state or is cancelled, it will notify the successor node so that the thread of the successor node can run */ static final int SIGNAL = -1; /** * The node is in the waiting queue, and the node thread is waiting on the Condition. When other threads call signal() on the Condition, the modified node will be transferred from the waiting queue to the synchronization queue and added to the acquisition of synchronization status */ static final int CONDITION = -2; /** * Indicates that the next shared synchronization state acquisition will propagate unconditionally */ static final int PROPAGATE = -3; /** Waiting state */ volatile int waitStatus; /** Precursor node */ volatile Node prev; /** Successor node */ volatile Node next; /** The thread that gets the synchronization status */ volatile Thread thread; Node nextWaiter; final boolean isShared() { return nextWaiter == SHARED; } final Node predecessor() throws NullPointerException { Node p = prev; if (p == null) throw new NullPointerException(); else return p; } Node() { } Node(Thread thread, Node mode) { this.nextWaiter = mode; this.thread = thread; } Node(Thread thread, int waitStatus) { this.waitStatus = waitStatus; this.thread = thread; } }
The structure diagram of CLH synchronization queue is as follows:
List
After learning the data structure, the CLH queue listing can be as simple as the tail pointing to the new node, the prev of the new node pointing to the last current node, and the next of the last current node pointing to the current node. In the code, we can see the addWaiter(Node node) method:
private Node addWaiter(Node mode) { //New Node Node node = new Node(Thread.currentThread(), mode); //Quick attempt to add tail node Node pred = tail; if (pred != null) { node.prev = pred; //CAS set tail node if (compareAndSetTail(pred, node)) { pred.next = node; return node; } } //Multiple attempts enq(node); return node; }
addWaiter(Node node) first sets the tail node through a quick attempt. If it fails, it calls enq(Node node) method to set the tail node
private Node enq(final Node node) { //Try many times until you succeed for (;;) { Node t = tail; //tail does not exist, set as the first node if (t == null) { if (compareAndSetHead(new Node())) tail = head; } else { //Set as tail node node.prev = t; if (compareAndSetTail(t, node)) { t.next = node; return t; } } } }
In the above code, both methods set the tail node through a CAS method compareAndSetTail(Node expect, Node update), which can ensure that the node is added thread safely. In enq(Node node) method, AQS ensures that nodes can be added correctly by means of "dead loop". The current thread will return from this method only after successful addition, otherwise it will be executed all the time.
The process diagram is as follows:
Out of line
The CLH synchronization queue follows the FIFO. After the thread of the first node releases the synchronization state, it will wake up its successor node (next), and the subsequent node will set itself as the first node when it succeeds in obtaining the synchronization state. This process is very simple. head can execute the node and disconnect the next of the original first node and the prev of the current node, Note that CAS is not required to guarantee this process, because only one thread can successfully obtain the synchronization state. The process diagram is as follows: