Use of LockSupport and Condition of Java JUC Concurrent Package Components

In this article, we only introduce part of the API and some concepts of LockSupport and Condition. No examples are involved. If you want an example, you can check the blocking queue part of the JDK source code. I also wrote an article about blocking queues.

In the Java thread state and life cycle article, when we introduced the Java life cycle, we introduced that when calling LockSupport, the thread will be in the WAITiNG state and return to the RUNNABLE state. LockSupport defines a set of public static methods. These methods provide the most basic thread blocking and wake-up functions. It is a basic tool for building synchronization components. LockSupport defines a set of methods starting with park to block the current thread, and unpark (Thread thread) method to wake up a blocked thread. The API methods provided by LockSupport are as follows:

API method

description

void park()

Block the current thread, call void unPack (Thread thread) or thread interruption before returning

void parkNanos(long nanos)

On the basis of void park(), the blocking time is added, and the timeout will be returned. The maximum blocking time does not exceed nanos seconds

void parkUntil(long deadline)

On the basis of void park(), a blocking time is added, and the timeout will also return. The maximum blocking time does not exceed the deadline.

void park(Object blocker)

These three methods are the same as the above three. The only difference is that the blocker is added to identify the object (blocking object) the current thread is waiting for.

void parkNanos(Object blocker,long nanos)

void parkUntil(Object blocker,long deadline)

void unPack(Thread thread)

Wake up threads in waiting state

 The use of LockSupport is relatively simple. Compared with the use of LockSupport, we use the Condition interface more. When introducing synchronized, we already know that synchronized combines wait(), wait(long timeout), notify() and notifyAll( The method can realize the waiting notification mechanism, and its realization is mainly realized by the monitor of the object. The same Condition also implements the waiting notification mechanism, but it is implemented using a waiting queue, and the content of AQS is used here. The following is the comparison between synchronized and Condition interface implementation:

 

When using the Condition interface, it must be obtained through the newCondition method of Lock. Later we will explain the newCondition method of Lock. Here we first introduce the methods provided by the Condition interface. The following table lists the methods provided by the Condition interface:

API method

description

void await()

Enter the waiting state until notified, or interrupted.

void awaitUninterruptibly()

Enter the waiting state until notified, not sensitive to interrupts

long awaitNanos(long nanosTimeout)

The current thread enters the waiting state until notified. Interrupt or timeout, if it returns 0 or a negative number, it means timeout, otherwise it returns the time consumed.

boolean await(long time, TimeUnit unit)

Same as awaitNanos(long nanosTimeout)

boolean awaitUntil(Date deadline)

The current thread enters the waiting state until notified. If interrupted or at a certain point in time, if there is no specified time point, it will be notified and return true, otherwise return false

void signal()

Wake up a thread waiting on the Condition, the thread must obtain the lock related to the Condition before returning from the waiting method

void signalAll()

Wake up all threads waiting on the Condition, the thread must obtain the lock related to the Condition before returning from the waiting method

We say that when acquiring Condition, a lock must be acquired. We use the newCondition method of ReentrantLock to see how Lock acquires the Condition instance. The code is as follows:

final ConditionObject newCondition() {
    return new ConditionObject();
}

The above code returns a ConditionObject instance. ConditionObject is an implementation of Condition, which is an internal class of AQS. Each Condition object contains a queue (hereinafter referred to as the waiting queue), which is the key to the Condition object's waiting/notification function. The following analysis of the implementation of Condition in combination with the source code mainly includes three directions: waiting queue, waiting and notification.

First of all, we look at the waiting queue of Condition. Those who have known about AQS may already have a certain understanding of waiting queue. Although Condition is a subclass of AQS, there are some differences between the waiting queue of Condition and the waiting queue of AQS. The following is the definition of ConditionObject in AQS:

public class ConditionObject implements Condition, java.io.Serializable {
    private static final long serialVersionUID = 1173984872572414699L;
    //队列中的第一个等待节点
    private transient Node firstWaiter;
    //队列中最后一个等待节点
    private transient Node lastWaiter;
    //构造方法
    public ConditionObject() { }
    ......
}

A Condition contains a waiting queue, and the Condition has a first node (firstWaiter) and a last node (lastWaiter). For the definition of Node, please refer to a blog of AQS: Queue Synchronizer AQS Principle Analysis-Exclusive acquisition of synchronization status . When the current thread calls the Condition.await() method, the node will be constructed with the current thread and the node will be added to the waiting queue from the tail. The following is the structure diagram of the waiting queue of Condition:

As shown in the figure, Condition has a reference to the head and tail nodes, and adding a new node only needs to point the original tail node nextWaiter to it and update the tail node. The difference from the synchronization queue is that a Lock instance is also an AQS instance. There can be multiple Condition waiting queues. The following figure shows the structure of AQS's synchronization queue and Condition queue:

When we actually call the Condition method starting with await, the current thread will enter the waiting queue and release the lock, and the thread state will change to the waiting state. We take await() as an example to analyze the waiting process of Condition from the perspective of source code. The code is as follows:

public final void await() throws InterruptedException {
    //从线程中断抛出异常
    if (Thread.interrupted())
        throw new InterruptedException();
    //将节点添加到等待队列
    Node node = addConditionWaiter();
    //释放锁
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    //如果不是同步队列类型的节点,将线程改为WAITING状态,
    //下面添加的节点的状态为Condition类型,不是同步类型
    while (!isOnSyncQueue(node)) {
        LockSupport.park(this);
            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);
}
//将节点添加到等待队列
private Node addConditionWaiter() {
    Node t = lastWaiter;
    // If lastWaiter is cancelled, clean out.
    if (t != null && t.waitStatus != Node.CONDITION) {
        unlinkCancelledWaiters();
        t = lastWaiter;
    }
    //创建节点实例,类型为Condition
    Node node = new Node(Thread.currentThread(), Node.CONDITION);
    if (t == null)
        firstWaiter = node;
    else
        t.nextWaiter = node;
        lastWaiter = node;
        return node;
}

The thread that called this method successfully acquired the lock thread, which is the first node in the synchronization queue. This method will construct the current thread into a node and add it to the waiting queue, then release the synchronization state, wake up the subsequent nodes in the synchronization queue, and then The current thread will enter the waiting state. The first node of the synchronization queue does not directly join the waiting queue, but constructs the current thread into a new node through the addConditionWaiter() method and adds it to the waiting queue. The structure of the queue is as follows:

Calling the signal() method of Condition will wake up the node that has been waiting for the longest time in the waiting queue (the first node). Before waking up the node, the node will be moved to the synchronization queue. We take signal() as an example as the source code:

public final void signal() {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    if (first != null)
    doSignal(first);
}

private void doSignal(Node first) {
    do {
       if ( (firstWaiter = first.nextWaiter) == null)
          lastWaiter = null;
          first.nextWaiter = null;
        }while (!transferForSignal(first) &&(first = firstWaiter) != null);
}
final boolean transferForSignal(Node node) {
    //如果不能改变节点状态,取消
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
        return false;

    //家人到同步队列
    Node p = enq(node);
    int ws = p.waitStatus;
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        //唤醒线程
        LockSupport.unpark(node.thread);
        return true;
}

The signal() method performs an isHeldExclusively() check to check whether the current thread is the thread that has acquired the lock. Then get the first node of the waiting queue, move it to the synchronization queue and use LockSupport to wake up the threads in the node. By calling the enq(Node node) method of the synchronizer, the head node thread in the waiting queue can be safely moved to the synchronization queue. When the node moves to the synchronization queue, the current thread uses LockSupport to wake up the node's thread. The structure diagram of wake-up is as follows:

This is the introduction to the waiting notification mechanism of LockSupport and Condition. If you want to use the example, it is recommended to read the source code of the blocking queue part, because the source part of the blocking queue uses a large number of Condition waiting notification mechanism and LockSupport. I won't list any examples here. 

Guess you like

Origin blog.csdn.net/wk19920726/article/details/108508677
Recommended