Condition variables Condition of concurrent programming source code analysis

Fanger Wei code or search public micro-channel number of the next scan 菜鸟飞呀飞, you can focus on micro-channel public number, read more Spring源码分析, and Java并发编程articles.

Micro-channel public number

AQS queue synchronizer is achieved by the tube model, the tube model, we mentioned the two queues: 入口等待队列and 条件变量等待队列. In AQS in 同步队列the corresponding tube-model 入口等待队列, 条件等待队列corresponding to the tube model 条件变量等待队列. The design principle and source about AQS synchronization queue implementations can read these two articles: design principles queue synchronizer (AQS) of and queue synchronizer (AQS) source code analysis . Today AQS waiting queue design principle and source code will be analyzed in detail. (On the introduction of the tube can refer to the article: the tube: the cornerstone of concurrent programming )

Brief introduction

  • Two concurrent problems in the field need to be addressed: 互斥the 同步mutually exclusive referring to the same time allowing only one thread to access shared resources, it AQS synchronization queue has helped us solve. Synchronization refers to how communication and collaboration between the threads, then AQS is how to solve synchronization problems? The answer is today's hero: Condition.
  • Condition is a packet interface in JUC, it achieved a class ConditionObjectis the queue synchronizer AbstractQueuedSynchronizer (later referred to as the AQS) an internal class. Used in conjunction with the Lock, through the Lock.newCondition()creation instance.
  • There are three methods in the native Object class: wait (), notify () , notifyAll (), are designed for use with synchronize keywords, use it to achieve communication between threads. Condition and provides await()、signal()、signalAll()three methods, which correspond to the three methods Object class, are used for the communication prior to the thread, but some differences exist in the function and they use a common manner. Specific differences may refer to the table. (Table from "Java concurrent programming Art," a book Chapter 5, Section 6)
Comparison items Object Condition
Use the premise Get use to synchronize lock Lock instance using lock () method to get the lock
Use object.wait()、object.notify()等 Need to use the Lock interface instance object is created, Lock.newCondition ()
The number of waiting queue Support a Can support multiple, new Lock instance use can be more Condition
Whether to respond to interrupt the thread into the wait queue not support stand by
Timeout waiting stand by stand by
After waiting thread releases the lock to a future point in time not support stand by
Wake up a thread waiting in the queue Support, notify () Support, signal ()
Wake up all threads waiting in the queue Support, notifyAll () Support, signalAll ()

AQS waiting in how to design the columns

data structure

  • AQS synchronization data structure is a doubly linked list queue Node element is formed, similarly, is a linked list queue in the Node element is formed, only a one-way linked list. In the queue synchronizer (AQS) design principles in this article attribute Node has been introduced, and today it's again review the properties and uses.
Property name effect
Node prev Synchronizing queue, the former current node, the first node queue if the current node is synchronized, then the attribute is null prev
Node next Synchronizing queue, after a current node, the tail current node if the node is a synchronous queue, then the next property is null
Node thread Thread the current node represents, if the current thread acquires the lock, then the node current thread represents certain in the team's first synchronized queue, and thread property is null, as to why you want to set it to null, which is AQS by design.
int waitStatus Wait state of the current thread, there are five kinds of values. 0 represents an initial value of 1 indicates that the thread is canceled, -1 represents the current thread in a wait state is -2 in queue for a node, a shared state at -3 retrieves synchronization will be propagated down unconditionally
Node nextWaiter Waiting queue, the next node of the node
  • AQS 等待队列were implemented using the node Node nextWaiterattributes and waitStatusproperties to achieve, wherein waitStatus=-2, it represents 线程是处于等待队列中. (Prev and next synchronization queue rely on property to implement a doubly linked list). Condition contains two properties: firstWaiterand lastWaiter, respectively, the first team and the tail of the queue. Queue waiting to follow the principle of first in first out (FIFO). The schematic below shows the Condition waiting queue.

Queuing waiting

The principle

  • Principle synchronize achieve lock is achieved through the tube model, but synchronize achieve lock, supports only one queue, and Condition can support multiple queue. In AQS, the sync queue and the queue is a schematic diagram below.

Synchronous queue and wait queue

  • When acquiring the lock thread through Lock, calling Condition of the await () method when the current thread will first have encapsulated into a Node node, then the node 添加到等待队列; then release the lock; and finally call LockSopport.park()the method will hang himself. It may be used as a schematic view of FIG.

the await () schematic

  • When calling the Condition signal () method removes the first queue of firstWaiterthe node, and then firstWaiteradded to the node 同步队列of. Diagram below.

signal () schematic

  • If you are calling Condition of signalAll () method, it will be waiting in the queue Condition 所有Node节点移到同步队列中.

Source code analysis

I appreciated that the above data structure and implementation principle of the queue, then look for binding source specific implementation. Next we will analyze source code await () method and signal () methods.

await () method

When calling Condition.await () method calls to await the internal class ConditionObject AbstractQueuedSynchornizer in () method. The source of the method are as follows.

public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    // 将当前线程加入到等待队列中
    Node node = addConditionWaiter();
    // 完全释放锁
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    // 判断同步节点是否在同步队列中,
    // 如果在当前线程释放锁后,锁被其他线程抢到了,此时当前节点就不在同步队列中了。
    // 如果锁没有被抢占到,节点就会再同步队列中(当前线程是持有锁的线程,所以它是头结点)
    while (!isOnSyncQueue(node)) {
        // 如果节点不在同步队列中,将当前线程park
        LockSupport.park(this);
        /**
         * 当被唤醒以后,接着从下面开始执行。醒来后会判断自己在等待过程中有没有被中断过。
         * checkInterruptWhileWaiting()方法返回0表示没有被中断过
         * 返回-1表示需要抛出异常
         * 返回1表示需要重置中断标识
         */
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    // 如果线程在同步队列中,那么就尝试去获取锁,如果获取不到,就会加入到同步队列中,并阻塞
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    // 当条件等待队列中还有其他线程在等待时,需要判断条件等待队列中有没有线程被取消,如果有,则将它们清除
    if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}
复制代码
  • In the await () method, the current thread will first call the addConditionWaiter()method, after their encapsulated into a Node, was added to the waiting queue. Then call the fullyRelease()method releases the lock, followed by judging whether the current thread in the waiting queue, if not in the waiting queue, turn yourself park.
  • In addConditionWaiter () method, mainly did two things. A: the node will wait queue deletion in a canceled state, i.e. waitStatus = Node 1 Node; Second: himself added to the waiting queue, so that the Condition lastWaiterattribute equal to the current node represents a thread. (Note: At this point if the queue is not initialized, will first be initialized). Source addConditionWaiter () method is as follows.
/**
 * Adds a new waiter to wait queue.
 * @return its new wait node
 * 将当前线程加入到等待队列中
 */
private Node addConditionWaiter() {
    Node t = lastWaiter;
    // If lastWaiter is cancelled, clean out.
    if (t != null && t.waitStatus != Node.CONDITION) {
        unlinkCancelledWaiters();
        t = lastWaiter;
    }
    // 创建一个节点,节点的waitStatus等于-2
    Node node = new Node(Thread.currentThread(), Node.CONDITION);
    // 如果等待队列还没有初始化,即等跌队列中还没有任何元素,那么此时firstWaiter和lastWaiter均为null
    if (t == null)
        firstWaiter = node;
    else
        t.nextWaiter = node; // 令旧的队尾节点nextWaiter属性等于当前线程的节点,这样就维护了队列的前后关系
    lastWaiter = node;
    return node;
}
复制代码
  • For the fullyRelease()method, the method name sees that its role is 完全释放锁. Why here fully release the lock it? Because for re-entry lock, the 锁可能被重入了多次,此时同步变量state的值大于1time, and calling await () method to let the current thread releases the lock out, so it is necessary to state the value was reduced to 0, so here named fullyRelease (). fullyRelease () eventually calls the AQS release()method to release the lock. Source fullyRelease () method is as follows.
final int fullyRelease(Node node) {
    boolean failed = true;
    try {
        // 获取同步变量state的值
        int savedState = getState();
        // 释放锁,注意此时将同步变量的值传入进去了,如果是重入锁,且被重入过,那么此时savedState的值大于1
        // 此时释放锁时,会将同步变量state的值减为0。(通常可重入锁在释放锁时,每次只会将state减1,重入了几次就要释放几次,在这里是一下子全部释放)。
        if (release(savedState)) {
            failed = false;
            return savedState;
        } else {
            // 当线程没有获取到锁时,调用该方法会释放失败,会抛出异常。
            throw new IllegalMonitorStateException();
        }
    } finally {
        if (failed)
            node.waitStatus = Node.CANCELLED;
    }
}
复制代码
  • In the await () method, When the lock release completion, followed by isOnSyncQueue()determination of the current thread 节点是不是在同步队列returns true if isOnSyncQueue () method, a node representing the sync queue, if it returns false, indicating that the current node is not in the synchronization queue. When returning false, 会进入到while循环中, this time will call LockSupport.park()the method, let the current thread suspended. Due to the current thread after thread releases the lock will wake up to grab lock synchronization queue, if there is a thread to grab the lock, then the current thread synchronization queue certainly not in the (first node synchronization queue changed), so in this case isOnSyncQueue () method returns a high probability false, and therefore will enter the while (method). If the current thread synchronization in the queue, or after waking up from the Park () (after waking up in sync queue appears as signal () or signalAll () method adds a node to a synchronous queue) will be executed to await () method logic behind that acquireQueued()method, which is to try to acquire the lock, if it will get to return, not to get blocked.
  • isOnSyncQueue()Source code and comment below.
final boolean isOnSyncQueue(Node node) {
    // 如果节点的waitStatus=-2时,节点肯定不在同步队列中,因为只有在等待队列时,才会为-2。
    // 如果节点的前驱节点为空时,有两种情况:
    // 1. 当前节点是同步队列的首节点,首节点是已经获取到锁的线程,可以认为线程不在同步队列中
    // 2. 当前节点在等待队列中,等待队列中的节点在创建时,没有给prev属性赋值
    if (node.waitStatus == Node.CONDITION || node.prev == null)
        return false;
    // 在上一个判断的前提条件下,如果后置节点不为空,那么当前节点肯定在同步队列中。
    if (node.next != null) // If has successor, it must be on queue
        return true;
    /*
     * node.prev can be non-null, but not yet on queue because
     * the CAS to place it on queue can fail. So we have to
     * traverse from tail to make sure it actually made it.  It
     * will always be near the tail in calls to this method, and
     * unless the CAS failed (which is unlikely), it will be
     * there, so we hardly ever traverse much.
     */
    // 以上情况均不属于,那么就从同步队列的尾部开始遍历,找到同步队列中是否含有node节点
    return findNodeFromTail(node);
}
复制代码
  • About await () method, summed up in three steps: 1.to add yourself to the waiting queue; 2.release the lock; 3.his park (). In Condition interface provides several overloaded the await () method, they are added on the basis of the await () method on some of the features, such as timeouts waiting, without waiting for response to interrupts and other functions, but the logic and substantially the await () like, friends who are interested can go to study under.

signal () method

  • When you call condition.signal()upon method calls to the internal class AbstractQueuedSynchornizer ConditionObject的signal()方法. The source of the method are as follows.
public final void signal() {
    // 先判断当前线程有没有获取到锁,如果没有获取到锁就来调用signal()方法,就会抛出异常
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    if (first != null)
        doSignal(first);
}
复制代码
  • In signal () method determines whether the current thread is the lock owner, if not, throw an exception. That is why calls await () and signal (), signalAll () method provided that: the current thread to acquire a lock, because these methods before you perform specific logic to determine whether the current thread is equal to the thread holding the lock . It can be seen from the code, the specific logic signal () method is doSignal()implemented in the method. Source doSignal () method is as follows.
private void doSignal(Node first) {
    do {
        // 令firstWaiter等于条件等待队列中的下一个节点。
        if ( (firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
        first.nextWaiter = null;
        /**
         *调用transferForSignal()方法,是将节点从条件等待队列中移到同步队列中.
         * 当transferForSignal()返回true时,表示节点被成功移到同步队列了。返回false,表示移动失败,当节点所表示的线程被取消时,会返回false
         * 当transferForSignal()返回true时,do...while循环结束。返回false时,继续。为什么要这样呢?
         * 因为当transferForSignal()返回false表示条件等待队列中的,队列的头结点的状态时取消状态,不能将它移到同步队列中,随意需要继续从条件等待队列找没有被取消的节点。
         */
    } while (!transferForSignal(first) &&
             (first = firstWaiter) != null);
}
复制代码
  • action doSignal () method is to wait queue head node 从等待队列中移除, and then calls transferForSignal()a method which 加入到同步队列, when () Returns true transferForSignal, indicates the node is successfully moved to the sync queue, returns false failure indicates movement, the situation will only one move failed, that thread is canceled. Source transferForSignal () method is as follows.
/**
 * Transfers a node from a condition queue onto sync queue.
 * Returns true if successful.
 * @param node the node
 * @return true if successfully transferred (else the node was
 * cancelled before signal)
 * 将节点从条件等待队列移到同步队列
 */
final boolean transferForSignal(Node node) {
    /*
     * If cannot change waitStatus, the node has been cancelled.
     */
    // 将节点的waitStatus的值从-2改为-1。这里如果出现CAS失败,说明节点的waitStatus值被修改过,在条件等待队列中,只有当线程被取消后,才会去修改waitStatus
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
        return false;

    /*
     * Splice onto queue and try to set waitStatus of predecessor to
     * indicate that thread is (probably) waiting. If cancelled or
     * attempt to set waitStatus fails, wake up to resync (in which
     * case the waitStatus can be transiently and harmlessly wrong).
     */
    // 当加入到同步队列后,需要将当前节点的前一个节点的waitStatus设置为-1,表示队列中还有线程在等待
    // 如果前驱节点的waitStatus大于0表示线程被取消,需要将当前线程唤醒
    // 或者修改前驱节点的waitStatus是失败,也需要去唤醒当前线程
    Node p = enq(node);
    int ws = p.waitStatus;
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        LockSupport.unpark(node.thread);
    return true;
}
复制代码
  • signal () Methods: The head of the wait queue node moves to the synchronous queue. Careful friends may find, call signal () method does not make the current thread releases the lock, so when the thread has finished the call signal () method, the current thread will own the business logic executed, until the call in the current thread the lock.unlock () method, will release the lock.

signalAll () method

  • signalAll()Method of action is 唤醒等待队列中的所有节点, and signal () method only wake up the first node waiting queue. When calling condition.signalAll () method calls the class ConditionObject signalAll inside the AbstractQueuedSynchornizer in () method. The source of the method are as follows.
public final void signalAll() {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    if (first != null)
        // 核心逻辑在doSignalAll()方法
        doSignalAll(first);
}
复制代码
  • In signalAll () method calls the doSignalAll()method. Source doSignalAll () method is as follows.
private void doSignalAll(Node first) {
    lastWaiter = firstWaiter = null;
    // 通过do...while循环,遍历等待队列的所有节点
    do {
        Node next = first.nextWaiter;
        first.nextWaiter = null;
        // transferForSignal()方法将节点移到到同步队列
        transferForSignal(first);
        first = next;
    } while (first != null);
}
复制代码
  • Can be found, doSignalAll () method do...whileloop, traversing all the nodes wait queues, cycle call transferForSignal()method, 等待队列中的节点全部移动到同步队列the.

to sum up

  • This paper describes the features AQS waiting queue, compare it with the corresponding Object of the similarities and differences method. Then wait queue data structure analysis, combined with a schematic view showing the principle of the await () method and a signal () method. Finally source to achieve a particular, detailed analysis await (), signal (), signalAll () of these three methods.
  • Condition very convenient to use, developers only need to Lock.newCondition()be able to create a Condition instance method, 多次调用Lock.newCondition () method, it will create 多个条件等待队列.
  • Condition wide range of applications, we often bounded queue contact LinkedBlockingQueueis achieved by Condition, friends who are interested can go read the source code under.

related suggestion

Micro-channel public number

Guess you like

Origin juejin.im/post/5dc04a136fb9a04aa333bfb8