Deeply understand the condition variable Condition

concept

Condition is the new standard library java.util.concurrent.locks.Condition interface introduced by JDK1.5.
The Condition interface can be used as a substitute for wait/notify to implement wait/notify. It provides support for solving the problem of premature wake-up , and solves the problem that Object.wait(long) cannot distinguish whether the return is due to wait timeout .
Condition instance can be obtained through Lock.newCondition(), which means that any newCondition method that displays a lock instance can obtain a Condition instance.
Object.wait/notify requires its execution thread to hold the internal lock of the object to which these methods belong, such as synchronize

use

Condition usage is similar to wait/notify. as follows:

Consumer .java

public class Consumer implements Runnable {
    
    
    private Queue<String> msg;
    private int maxSize;
    private Lock lock;
    private Condition condition;

    public Consumer(Queue<String> msg, int maxSize, Lock lock, Condition condition) {
    
    
        this.msg = msg;
        this.maxSize = maxSize;
        this.lock = lock;
        this.condition = condition;
    }
    @Override
    public void run() {
    
    
        while (true) {
    
    
            lock.lock();
            while (msg.isEmpty()) {
    
    
                //如果消息队列为空了
                try {
    
    
                    System.out.println("第一次阻塞");
                    condition.await(); //阻塞当前线程
                    System.out.println("第二次唤醒");
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
            try {
    
    
                Thread.sleep(1000);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            System.out.println("消费者消费消息:" + msg.remove());
            condition.signal(); //唤醒处于阻塞状态下的生产者
            lock.unlock();
        }
    }
}

Product .java

public class Product implements Runnable{
    
    
    private Queue<String> msg;
    private int maxSize;
    private Lock lock;
    private Condition condition;

    public Product(Queue<String> msg, int maxSize, Lock lock, Condition condition) {
    
    
        this.msg = msg;
        this.maxSize = maxSize;
        this.lock = lock;
        this.condition = condition;
    }

    @Override
    public void run() {
    
    
        int i=0;
        while(true){
    
    
            i++;
            lock.lock();
            while(msg.size()==maxSize){
    
    
                //如果生产满了
                try {
    
    
                   condition.await();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
            try {
    
    
                Thread.sleep(1000);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            System.out.println("生产者生产消息:"+i);
            msg.add("生产消息:"+i);
            condition.signal();
            lock.unlock();
        }
    }
}

Source code analysis

Note: I will comment out the function of each method in the source code, you can refer to the comments for understanding. The explanation after the code will be as little as possible.

Next we focus on two methods, which are also the two most commonly used methods: await() and signal()

await()

public final void await() throws InterruptedException {
    
    
	//线程中断,抛出异常
   if (Thread.interrupted())
        throw new InterruptedException();
    //创建node节点,并将此节点加到Condition下的队列的最后一个
    Node node = addConditionWaiter();
    //让持有该锁的所有线程释放锁(包括重入),同时去唤醒AQS队列中的一个线程
    long savedState = fullyRelease(node);
    int interruptMode = 0;
    //判断此节点是否在AQS队列中
    while (!isOnSyncQueue(node)) {
    
    
     //第一次进来,肯定会返回false,接着将自己阻塞,等待被唤醒
        LockSupport.park(this);
        //signal唤醒之后,继续往下执行
        //判断在等待过程中线程是否被中断,如果线程没中断,则会继续执行循环
        //再次去isOnSyncQueue判断,改节点是否在AQS中
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
        	//表示线程被中断,跳出循环
            break;
    }
    // 当这个线程醒来,会尝试拿锁, 当 acquireQueued 返回 false 就是拿到锁了.
    // 将这个变量设置成 REINTERRUPT.表示需要重新中断,不会抛出异常
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
     //清理掉当前节点之后状态为cancelled的节点
    if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters();
    // 如果线程被中断了,需要抛出异常.或者什么都不做
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}

Come in, first judge whether the current thread has an interrupt mark, if so, throw an interrupt exception directly and hand it over to the upper layer for processing; otherwise, it needs to be executedaddConditionWaiterMethod to create a node, and load it into the Condition queue

addConditionWaiter()

private Node addConditionWaiter() {
    
    
   //获取最后一个节点
   Node t = lastWaiter;
    // 如果最后一个节点的状态是cancelled,直接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;
}

If the status of the last node is cancelled , it will be cleaned up directly. Then create the node of the current thread, and initialize the state of the current node to CONDITION.

  • If the last node is empty, this will assign the current node to firstWaiter and at the same time assign the current node to lastWaiter
  • If it is not empty, after adding the current node to the lastWaiter node, assign the current node to lastWaiter

Immediately afterwards, we must release the lock

fullyRelease(node)

final long fullyRelease(Node node) {
    
    
    boolean failed = true;
    try {
    
    
    	//获得当前锁的状态值state
        long savedState = getState();
        //去释放锁,包括重入锁,全部清0
        if (release(savedState)) {
    
    
            failed = false;
            return savedState;
        } else {
    
    
            throw new IllegalMonitorStateException();
        }
    } finally {
    
    
    	//如果失败,将当前节点状态置为CANCELLED
        if (failed)
            node.waitStatus = Node.CANCELLED;
    }
}
public final boolean release(long arg) {
    
    
     if (tryRelease(arg)) {
    
    
         Node h = head;
         if (h != null && h.waitStatus != 0)
             unparkSuccessor(h);
         return true;
     }
     return false;
}

Release here, just talking about before ReentrantLock is the same, not here in duplicate, before you can go directly to the article in-depth understanding ReentrantLock

isOnSyncQueue (node)

final boolean isOnSyncQueue(Node node) {
    
    
		//判断当前节点的状态
		//第一次进来的时候状态肯定为CONDITION ==》返回false
        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可以为非null,但尚未排队,
        //因为将CAS放入队列的CAS可能会失败。 
        //因此,我们必须从尾部开始遍历以确保它确实做到了。 
        //在此方法的调用中,它将始终处于尾部,除非CAS失败(这不太可能),
        //否则它将一直存在,因此我们几乎不会遍历太多。
        return findNodeFromTail(node);
    }

In fact, the fasle is always returned the first time, thus entering the loop and blocking itselfLockSupport.park(this), At this point, Condition successfully released the Lock lock and blocked itself.
After waking up, the following if method will be executed

checkInterruptWhileWaiting(node)

private int checkInterruptWhileWaiting(Node node) {
    
    
	 //是否有中断标记
   return Thread.interrupted() ?
        (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
        0;
}

The meanings of THROW_IE and REINTERRUPT are as follows:

  • THROW_IE: InterruptedException is thrown while waiting for exit
  • REINTERRUPT: Re-interrupt while waiting to exit, that is, no operation.
    Re-check the interrupt status. If interrupted, execute the ==transferAfterCancelledWait(node)== method to change the node status to 0 and add it to the AQS queue at the same time
final boolean transferAfterCancelledWait(Node node) {
    
    
	//CAS操作将node状态变为0
	//然后添加到AQS的队列中
   if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
    
    
   		//添加到AQS的队列中
        enq(node);
        return true;
    }
    //如果我们失败,那么我们将无法继续
    //直到完成其enq()。 
    //因为几乎是不可能会失败的,所以采用自旋的方式
    while (!isOnSyncQueue(node))
        Thread.yield();
    return false;
}

After exiting the while loop, continue to executeacquireQueued(node, savedState)Method to preempt the lock, but also to determine that the interruptMode value state cannot be equal to THROW_IE, which is the interrupt state.
After both methods are successful. The interruptMode value to REINTERRUPT , i.e. no impact, no operation.
Ways to preempt the lockacquireQueued(node, savedState)Also in the previous article in-depth understanding of ReentrantLock are mentioned, no explanation here.
Finally, it is to clean up the nodes whose status is CANCELLED behind the current node

to sum up

  • In Condition, a queue is maintained. When the await method is executed, a node is created and added to the tail.
  • Then release the lock and wake up a thread blocked in the lock's AQS queue, which is consistent with ReentrantLock logic
  • Block yourself
  • After being awakened by other threads, put the node just now in the AQS queue

Next, let’s see how it is awakened

signal()

public final void signal() {
    
    
   // 如果当前线程不是持有该锁的线程.抛出异常
   if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    //获得第一个节点
    Node first = firstWaiter;
    if (first != null)
        doSignal(first);
}

Wake up from the head

private void doSignal(Node first) {
    
    
   do {
    
    
   		//第一个节点的下一个节点为null
   		//将lastWaiter变为nul
        if ( (firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
        first.nextWaiter = null;
    } while (!transferForSignal(first) &&
             (first = firstWaiter) != null);
}

Using a do while loop, first determine the next node of the current node, and then executetransferForSignal(node) method

transferForSignal(node)

final boolean transferForSignal(Node node) {
    
    
    //如果CAS将状态值变为0失败,则返回false,继续循环,类似自旋
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
        return false;

   //将节点node加到AQS队列
   //返回node的前一个节点
    Node p = enq(node);
    int ws = p.waitStatus;
    //CAS将前一个节点状态置为SIGNAL状态,唤醒当前线程
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        LockSupport.unpark(node.thread);
    return true;
}

Finally usedLockSupport.unpark(node.thread) Method to wake up the thread on the node node

Guess you like

Origin blog.csdn.net/xzw12138/article/details/106469832