AQS——Condition源码分析

文章目录

      • 1.初识Condition
        • 2.源码解析
          • 1ConditionObject 的实例
          • 2.ConditionObject内部类
          • 3.await
            • 3.1.addConditionWaiter()
            • 3.2 unlinkCancelledWaiters()
            • 3.3.fullyRelease(Node node)
            • 3.4isOnSyncQueue(Node node)
          • 4 signal()唤醒线程,转移到阻塞队列
            • 4.1 线程被唤醒后
            • 4.2.1

1.初识Condition

Condition 经常可以用在生产者-消费者的场景中

Condition提供了signal和await方法是用来配合锁 实现线程 间同 步 的基础设施。都必须获取到锁才能进行操作。Object的notify 和 wait,是配 合 synchronized 内置锁实现线程间同步 的基础设施一样。不同在于, synchronized同时只能与一个共享变量的notify或wait方法实现同步, 而 AQS 的一个锁可以对应多个条件变量。

public class ProductCoustomerDemo {

    private int num=0;
    private Lock lock=new ReentrantLock();//获取锁
    private Condition condition =lock.newCondition();//获取Condition例子,在使用 condition 时,必须先持有相应的锁

    public void doPro(){
        lock.lock();
        try {
            while (num!=10){
                condition.await();
            }
            num++;
            System.out.println(Thread.currentThread().getName()+"生产了"+num);
            condition.signalAll();
        }catch (Exception e){

        }finally {
            lock.unlock();
        }
    }

    public void doCustomer(){
        lock.lock();
        try {

            while (num==10){
                condition.await();
            }
            num--;
            System.out.println(Thread.currentThread().getName()+"消费了"+num);
            condition.signalAll();
        }catch (Exception e){

        }finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        ProductCoustomerDemo productCoustomerDemo=new ProductCoustomerDemo();
        new  Thread(() -> {
            for (int i = 0; i < 5; i++) {
                productCoustomerDemo.doPro();
            }
        },"AA").start();

        new  Thread(()->{
            for (int i = 0; i < 5; i++) {
                productCoustomerDemo.doCustomer();
            }
        },"BB").start();
    }

​ lock.newCondition()的作用 其实是 new 了 一 个在 AQS 内部 声 明的 ConditionObject 对 象 , ConditionObject 是 AQS 的内部 类 ,可以访问 AQS 内部的变 量(例 如状态变量 state)和方法 。在每个条件变量 内部都维护了 一个条件队列用来存放调用条 件变量的 await()方法时被阻塞的线程

2.源码解析

1ConditionObject 的实例

1.每个 ReentrantLock 实例可以对应多个条件变量。调用多次 newCondition 产生多个 ConditionObject 的实例。

final ConditionObject newCondition() {
            return new ConditionObject();
}
2.ConditionObject内部类
 public class ConditionObject implements Condition, java.io.Serializable {
        private static final long serialVersionUID = 1173984872572414699L;
        /** First node of condition queue. */
        private transient Node firstWaiter;
        /** Last node of condition queue. */
        private transient Node lastWaiter;

AQS 的时候,我们有一个阻塞队列,用于保存等待获取锁的线程的队列,叫条件队列,一个锁对应一个 AQS 阻塞队列,对应多个条 件变量, 每个条件变量有自己的一个条件队列。

[外链图片转存失败(img-VKfBtJdn-1569164110083)(/Users/laiyanxin/Library/Application Support/typora-user-images/image-20190922220514658.png)]

3.await
public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
 	 //创建新的node节点,并插入到条件队列末尾
    Node node = addConditionWaiter();
  	//释放当前线程获取锁
    int savedState = fullyRelease(node);
    int interruptMode = 0;
  	//判断 当前节点是否在同步队列,如果不在就挂起 	
    while (!isOnSyncQueue(node)) {
      //调用park方法阻塞拉起当前线程
        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);
}
3.1.addConditionWaiter()
// 将当前线程对应的节点入队,插入队尾 
private Node addConditionWaiter() {
            Node t = lastWaiter;
            // 如果 最后一个节点是取消了 ,将其清除出去
            if (t != null && t.waitStatus != Node.CONDITION) {
              	//遍历整个条件 队列,然后 将已取消的节点 清除出去 队列
                unlinkCancelledWaiters();
                t = lastWaiter;
            }
  				//新建 一个节点,指定 waitStatus 为 Node.CONDITION
            Node node = new Node(Thread.currentThread(), Node.CONDITION);//  t 此时是 lastWaiter,队尾
            if (t == null)
                firstWaiter = node;
            else
                t.nextWaiter = node;
            lastWaiter = node;
            return node;
        }
3.2 unlinkCancelledWaiters()

用于清除队列中已经取消等待的节点。

//  清除队列中已经取消等待的节点
private void unlinkCancelledWaiters() {
            Node t = firstWaiter;
            Node trail = null;
            while (t != null) {
                Node next = t.nextWaiter;
                if (t.waitStatus != Node.CONDITION) {
                    t.nextWaiter = null;
                    if (trail == null)
                        firstWaiter = next;
                    else
                        trail.nextWaiter = next;
                    if (next == null)
                        lastWaiter = trail;
                }
                else
                    trail = t;
                t = next;
            }
        }
3.3.fullyRelease(Node node)

是完全释放独占锁,返回释放锁之前state的值,如果释放锁之前,state=2,说明是可重入锁,则返回savedState是 2,当它被唤醒的时候,它需要重新持有 2 把锁,才能继续下去。

//是完全释放独占锁,返回释放锁之前state的值,如果释放锁之前,state=2,说明是可重入锁,则返回savedState是 2
final int fullyRelease(Node node) {
        boolean failed = true;
        try {
            int savedState = getState();
          // 这里使用了当前的 state 作为 release 的参数,也就是完全释放掉锁,将 state 置为 0
            if (release(savedState)) {
                failed = false;
                return savedState;
            } else {
          // 如果不持有锁,会将节点设置为"取消"状态,并抛出异常 IllegalMonitorStateException
                throw new IllegalMonitorStateException();
            }
        } finally {
            if (failed)
                node.waitStatus = Node.CANCELLED;
        }
    }
3.4isOnSyncQueue(Node node)

用于判断节点是否已经转移到阻塞队列了

// 移动过去的时候,node 的 waitStatus 会置为 0,这个之后在说 signal 方法的时候会说到
    // 如果 waitStatus 还是 Node.CONDITION,也就是 -2,那肯定就是还在条件队列中
    // 如果 node 的前驱 prev 指向还是 null,说明肯定没有在 阻塞队列(prev是阻塞队列链表中使用的)   
final boolean isOnSyncQueue(Node node) {
        if (node.waitStatus == Node.CONDITION || node.prev == null)
            return false;
   // 如果 node 已经有后继节点 next 的时候,那肯定是在阻塞队列了
        if (node.next != null) // If has successor, it must be on queue
            return true;
     
//  这个方法从阻塞队列的队尾开始从后往前遍历找,如果找到相等的,说明在阻塞队列,否则就是不在阻塞队列
        return findNodeFromTail(node);
    }

isOnSyncQueue(node) 返回 false 的话,那么进到 LockSupport.park(this); 这里线程挂起。

4 signal()唤醒线程,转移到阻塞队列

这个 方法是 唤醒等待了最久的线程,将这个线程对应的 node 从条件队列转移到阻塞队列

 public final void signal() {
   //判断 获取锁的线程是不是当前线程
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
     
     Node first = firstWaiter;
            if (first != null)
                doSignal(first);
        }


  private void doSignal(Node first) {
            do {
              //判断第一个节点的下个节点是不是null,是null说明后面没有节点在等待了
              //将 lastWaiter 置为 null
              //设置当前first的下个节点也为null,脱离节点
                if ( (firstWaiter = first.nextWaiter) == null)
                    lastWaiter = null;
                first.nextWaiter = null;
            } while (!transferForSignal(first) &&
                     (first = firstWaiter) != null);
       // 这里 while 循环,如果 first 转移不成功,那么选择 first 后面的第一个节点进行转移,依此类推
        }


 final boolean transferForSignal(Node node) {
      // CAS 如果失败,说明此 node 的 waitStatus 已不是 Node.CONDITION,说明节点已经取消,
    // 既然已经取消,也就不需要转移了,方法返回,转移后面一个节点
    // 否则,将 waitStatus 置为 0
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;

     
   // enq(node): 自旋进入阻塞队列的队尾
    // 注意,这里的返回值 p 是 node 在阻塞队列的前驱节点
    Node p = enq(node);
    int ws = p.waitStatus;
    // ws > 0 说明 node 在阻塞队列中的前驱节点取消了等待锁,直接唤醒 node 对应的线程。唤醒之后会怎么样,后面再解释
    // 如果 ws <= 0, 那么 compareAndSetWaitStatus 将会被调用,上篇介绍的时候说过,节点入队后,需要把前驱节点的状态设为 Node.SIGNAL(-1)
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        // 如果前驱节点取消或者 CAS 失败,会进到这里唤醒线程,之后的操作看下一节
        LockSupport.unpark(node.thread);
    return true;
    }
4.1 线程被唤醒后
 while (!isOnSyncQueue(node)) {
                LockSupport.park(this);
   //线程被唤醒后 ,继续执行以下代码
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }	
 
// 1. 如果在 signal 之前已经中断,返回 THROW_IE
// 2. 如果是 signal 之后中断,返回 REINTERRUPT
// 3. 没有发生中断,返回 0
private int checkInterruptWhileWaiting(Node node) {
    return Thread.interrupted() ?
        (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
        0;
}

先解释下 interruptMode。interruptMode 可以取值为 REINTERRUPT(1),THROW_IE(-1),0

  • REINTERRUPT: 代表 await 返回的时候,需要重新设置中断状态
  • THROW_IE: 代表 await 返回的时候,需要抛出 InterruptedException 异常
  • 0 :说明在 await 期间,没有发生中断

Thread.interrupted():如果当前线程已经处于中断状态,那么该方法返回 true,同时将中断状态重置为 false,所以,才有后续的 重新中断(REINTERRUPT) 的使用。

看看怎么判断是 signal 之前还是之后发生的中断:

final boolean transferForSignal(Node node) {
// 只有线程处于中断状态,才会调用此方法
// 如果需要的话,将这个已经取消等待的节点转移到阻塞队列
// 返回 true:如果此线程在 signal 之前被取消,
final boolean transferAfterCancelledWait(Node node) {
    // 用 CAS 将节点状态设置为 0 
    // 如果这步 CAS 成功,说明是 signal 方法之前发生的中断,因为如果 signal 先发生的话,signal 中会将 waitStatus 设置为 0
    if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
        // 将节点放入阻塞队列
        // 这里我们看到,即使中断了,依然会转移到阻塞队列
        enq(node);
        return true;
    }

    // 到这里是因为 CAS 失败,肯定是因为 signal 方法已经将 waitStatus 设置为了 0
    // signal 方法会将节点转移到阻塞队列,但是可能还没完成,这边自旋等待其完成
    // 当然,这种事情还是比较少的吧:signal 调用之后,没完成转移之前,发生了中断
    while (!isOnSyncQueue(node))
        Thread.yield();
    return false;
}
4.2.1
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
       interruptMode = REINTERRUPT;
   if (node.nextWaiter != null) // clean up if cancelled
       unlinkCancelledWaiters();
   if (interruptMode != 0)
       reportInterruptAfterWait(interruptMode)

这里的 acquireQueued(node, savedState) 的第一个参数 node 之前已经经过 enq(node) 进入了队列,参数 savedState 是之前释放锁前的 state,这个方法返回的时候,代表当前线程获取了锁,而且 state == savedState了。

本文参考https://www.javadoop.com/post/AbstractQueuedSynchronizer-2
书籍《java并发编程之美》

你可能感兴趣的:(多线程)