Condition的使用和理解

接上一篇AQS,所以编号从四开始

四,Condition

在前面学习 synchronized 的时候,有讲到 wait/notify 的基本使用,结合
synchronized 可以实现对线程的通信。那么这个时候我就在思考了,既然 J.U.C 里
面提供了锁的实现机制,那 J.U.C 里面有没有提供类似的线程通信的工具呢? 于
是找阿找,发现了一个 Condition 工具类。
Condition 是一个多线程协调通信的工具类,可以让某些线程一起等待某个条件
(condition),只有满足条件时,线程才会被唤醒

1.ConditionWait

public class ConditionDemoWait implements Runnable{
     private Lock lock;
     private Condition condition;
     public ConditionDemoWait(Lock lock, Condition condition){
         this.lock=lock;
         this.condition=condition;
     }
     @Override
     public void run() {
        System.out.println("begin -ConditionDemoWait");
         try {
             lock.lock();
             condition.await();//阻塞
             System.out.println("end - ConditionDemoWait");
         } catch (InterruptedException e) {
            e.printStackTrace();
         }finally {
            lock.unlock();
         }
     } 
    
}

2. ConditionSignal

public class ConditionDemoSignal implements Runnable{
     private Lock lock;
     private Condition condition;
     public ConditionDemoSignal(Lock lock, Condition condition){
         this.lock=lock;
         this.condition=condition;
     }
     @Override
     public void run() {
        System.out.println("begin -ConditionDemoSignal");
        try {
            lock.lock();
            condition.signal();
            System.out.println("end - ConditionDemoSignal");
        }finally {
            lock.unlock();
        }
    }
}

总结:通过这个案例简单实现了 wait 和 notify 的功能,当调用 await 方法后,当前线程
会释放锁并等待,而其他线程调用 condition 对象的 signal 或者 signalall 方法通
知并被阻塞的线程,然后自己执行 unlock 释放锁,被唤醒的线程获得之前的锁继
续执行,最后释放锁。所以,condition 中两个最重要的方法,一个是 await,一个是 signal 方法
await:把当前线程阻塞挂起signal:唤醒阻塞的线程

3.Condition 源码分析

调用 Condition,需要获得 Lock 锁,所以意味着会存在一个 AQS 同步队列,先来看 Condition.await 方法
**1. condition.await **

调用 Condition 的 await()方法(或者以 await 开头的方法),会使当前线程进入等
待队列并释放锁,同时线程状态变为等待状态。当从 await()方法返回时,当前线程一定获取了 Condition 相关联的锁

public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    Node node = addConditionWaiter(); //创建一个新的节点,节点状态为 condition,采用的数据结构仍
    然是链表
    int savedState = fullyRelease(node); //释放当前的锁,得到锁的状态,并唤醒 AQS 队列中的一个线程
    int interruptMode = 0;
    //如果当前节点没有在同步队列上,即还没有被 signal,则将当前线程阻塞
    while (!isOnSyncQueue(node)) {//判断这个节点是否在 AQS 队列上,第一次判断的是 false,因为前面已
    经释放锁了
    LockSupport.park(this); // 第一次总是 park 自己,开始阻塞等待
    // 线程判断自己在等待过程中是否被中断了,如果没有中断,则再次循环,会在 isOnSyncQueue 中判断自己是否在队列上.
    // isOnSyncQueue 判断当前 node 状态,如果是 CONDITION 状态,或者不在队列上了,就继续阻塞.
    // isOnSyncQueue 判断当前 node 还在队列上且不是 CONDITION 状态了,就结束循环和阻塞.
    if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
    break;
    }
    // 当这个线程醒来,会尝试拿锁, 当 acquireQueued 返回 false 就是拿到锁了.
    // interruptMode != THROW_IE -> 表示这个线程没有成功将 node 入队,但 signal 执行了 enq 方法让
    其入队了.
    // 将这个变量设置成 REINTERRUPT.
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
    interruptMode = REINTERRUPT;
    // 如果 node 的下一个等待者不是 null, 则进行清理,清理 Condition 队列上的节点. 
    // 如果是 null ,就没有什么好清理的了.
    if (node.nextWaiter != null) // clean up if cancelled
    unlinkCancelledWaiters();
    // 如果线程被中断了,需要抛出异常.或者什么都不做
    if (interruptMode != 0)
    reportInterruptAfterWait(interruptMode);
}

**2. Condition.signal **

调用 Condition 的 signal()方法,将会唤醒在等待队列中等待时间最长的节点(首节点),在唤醒节点之前,会将节点移到同步队列中

public final void signal() {
    if (!isHeldExclusively()) //先判断当前线程是否获得了锁
        throw new IllegalMonitorStateException();
    Node first = firstWaiter; // 拿到 Condition 队列上第一个节点
    if (first != null)
        doSignal(first);
}

3. Condition.doSignal

private void doSignal(Node first) {
    do {
        // 如果第一个节点的下一个节点是 null, 那么,最后一个节点也是 null.
        if ( (firstWaiter = first.nextWaiter) == null)
            lastWaiter = null; // 将 next 节点设置成 null
            first.nextWaiter = null;
    } while (!transferForSignal(first) &&(first = firstWaiter) != null);
}

该方法先是 CAS 修改了节点状态,如果成功,就将这个节点放到 AQS 队列中,
然后唤醒这个节点上的线程。此时,那个节点就会在 await 方法中苏醒

    final boolean transferForSignal(Node node) {
    /*
    * If cannot change waitStatus, the node has been cancelled.
    */
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
    return false;
    Node p = enq(node);
    int ws = p.waitStatus;
    // 如果上一个节点的状态被取消了, 或者尝试设置上一个节点的状态为 SIGNAL 
    失败了(SIGNAL 表示: 他的 next 节点需要停止阻塞), 
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, 
    Node.SIGNAL))
    LockSupport.unpark(node.thread); // 唤醒输入节点上的线程.
    return true; 
        
}

4. AQS.transferForSignal
该方法先是 CAS 修改了节点状态,如果成功,就将这个节点放到 AQS 队列中,
然后唤醒这个节点上的线程。此时,那个节点就会在 await 方法中苏醒

final boolean transferForSignal(Node node) {
    /*
    * If cannot change waitStatus, the node has been cancelled.
    */
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
        return false;
    Node p = enq(node);
    int ws = p.waitStatus;
    // 如果上一个节点的状态被取消了, 或者尝试设置上一个节点的状态为 SIGNAL 失败了(SIGNAL 表示: 他的 next 节点需要停止阻塞), 
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        LockSupport.unpark(node.thread); // 唤醒输入节点上的线程.
    return true; 
    
}

Condition 总结
阻塞:await()方法中,在线程释放锁资源之后,如果节点不在 AQS 等待队
列,则阻塞当前线程,如果在等待队列,则自旋等待尝试获取锁
释放:signal()后,节点会从 condition 队列移动到 AQS 等待队列,则进入
正常锁的获取流程

你可能感兴趣的:(并发编程)