AQS principle analysis of queue synchronizer-exclusive acquisition of synchronization status

In the Simple Use of Queue Synchronizer AbstractQueuedSynchronizer, we briefly introduce the use of AbstractQueuedSynchronizer. In this article, we combine the examples in the previous article to introduce the principle of AQS. In the previous article, one word we have been mentioning is the waiting queue. If the lock acquisition fails, the current thread will be placed in the waiting queue. So what is the waiting queue in AbstractQueuedSynchronizer? Before we first introduce the synchronization queue.

The synchronization queue is a FIFO two-way queue used to complete the management of the synchronization state. When the current thread fails to obtain the synchronization state, the synchronizer will construct the current thread and waiting state information into a node (Node) and add it to the synchronization queue , It will block the current thread at the same time. When the synchronization state is released, the thread in the first node will be awakened to make it try to obtain the synchronization state again. Node is an internal class in AbstractQueuedSynchronizer, defined as follows:

  static final class Node {
        static final int CANCELLED =  1;
        static final int SIGNAL    = -1;
        static final int CONDITION = -2;
        static final int PROPAGATE = -3;
        /**
         * 等待状态
         *   SIGNAL:值为-1,后继节点的线程处于等待状态,而当前节点的线程如果释放可同步状态或者
         * 被取消,将会通知后继节点,使得后继节点的线程得以运行。
         *   CANCELLED:  值为1,由于在同步队列中等待的线程等待超时或者被中断,需要从同步队列中    
         * 取消等待,节点进入该状态将不会变化
         *   CONDITION: 值为-2,节点在等待队列中,节点线程等待在Condition上,当其他线程对
         * Condition调用了signal()方法之后,该节点将会从等待队列转移到同步队列中,加入到同步队
         * 列中的获取
         *   PROPAGATE: 值为-3表示下一次共享式同步状态获取将会无条件的传播下去
         *
         */
        volatile int waitStatus;

        /**
         * 前驱节点,当前节点加入同步队列时被设置,在尾部添加
         */
        volatile Node prev;

        /**
         * 后继节点
         */
        volatile Node next;

        /**
         * 获取同步状态的线程
         */
        volatile Thread thread;

        /**
         * 等待队列中的后继节点,如果当前节点是共享的那么该字段为SHARED常量,也就是说
         * 节点类型(独占和共享)和等待队列中的后继节点共用一个字段
         */
        Node nextWaiter;
        ......
}

The node is the basis of the synchronization queue (waiting queue). The synchronizer has the first node and the tail node. If the synchronization state is not obtained, the thread will become the end of the node to join the queue. The basic structure of the synchronization queue is shown in the following figure:

Below we combine the code to analyze how the synchronizer changes the synchronization state and adds threads to the synchronization queue and waiting queue. Because the synchronization state obtained by the synchronizer is divided into exclusive acquisition and release synchronization state, shared acquisition and release synchronization state, and query of the waiting thread in the synchronization queue. Therefore, we also analyze these two aspects. This blog mainly introduces the acquisition and release of exclusive lock synchronization state. Before again we need to understand the template method of the synchronizer. If you are not familiar with it, please refer to the blog of " The Simple Use of Queue Synchronizer AbstractQueuedSynchronizer ". When we get the synchronization status, we call the template method as shown in the following code:

//独占式获取锁
public final void acquire(int arg) {
    //调用重写方法获取同步状态,如果没有获取到同步状态,将当前线程加入到等待队列
    if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

In the above method, the rewrite method tryAquire(arg) is called to obtain the synchronization status. If the acquisition fails, the thread is added to the waiting queue through the acquireQueued(addWaiter(Node.EXCLUSIVE), arg) operation. Below we mainly analyze the added to the waiting queue process. The first is the AddWaiter method, the code is as follows:

private Node addWaiter(Node mode) {
    //将当前线程封装到Node中
    Node node = new Node(Thread.currentThread(), mode);
    //将节点添加到尾节点
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    enq(node);
    return node;
}
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;
            }
        }
    }
}

The above code uses the compareAndSetTail (Node expect, Node update) method to ensure that the node can be thread-safely added. In the enq (final Node node) method, the synchronizer uses the "infinite loop" to ensure the correct addition of the node. In the "infinite loop", only after the node is set to the tail node through CAS, the current thread can return from this method, otherwise , The current thread keeps trying to set. After a node enters the synchronization queue, it enters a spinning process. Each node (or each thread) is introspectively observing. When the conditions are met and the synchronization state is obtained, it can exit from this spinning process. Otherwise, it will remain in the spinning process (and will block the node's thread). This part of the code logic in the acquireQueued method is as follows:

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);
    }
}

In the acquireQueued(final Node node, int arg) method, the current thread tries to obtain the synchronization state in an "infinite loop", and only the predecessor node is the head node can try to obtain the synchronization state, as shown in the figure below, the synchronization state is obtained exclusively The flow chart:

In the above figure, the predecessor node is the head node and can obtain the judgment condition of the synchronization state and the thread enters the waiting state is the spin process of obtaining the synchronization state. When the synchronization status is successfully acquired, the current thread returns from the acquire(int arg) method. For a concurrent component such as a lock, it means that the current thread has acquired the lock. After the current thread obtains the synchronization state and executes the corresponding logic, it needs to release the synchronization state so that subsequent nodes can continue to obtain the synchronization state. The synchronization state can be released by calling the release(int arg) method of the synchronizer. After the synchronization state is released, this method will wake up its successor nodes (and then make the successor nodes try to obtain the synchronization status again). The code is as follows:

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
            return true;
        }
        return false;
    }

In AbstractQueuedSynchronizer, it is mainly the addition of synchronization queues. When reading the source code, understand the attributes in the Node class and the meaning of the attributes in AbstractQueuedSynchronizer. The main fields of AbstractQueuedSynchronizer are as follows:

//头结点,指向第一个等待节点
private transient volatile Node head;
//尾节点,指向最后一个等待节点
private transient volatile Node tail;
//同步状态
private volatile int state;

 

Guess you like

Origin blog.csdn.net/wk19920726/article/details/108370084