AbstractQueuedSynchronizer——JUC包的基石(下)

ConditionObject

public class ConditionObject implements Condition, java.io.Serializable
  1. AbstractQueuedSynchronizer中有一个内部类ConditionObject,该类实现了Condition接口。JUC包中,ConditionObject是Condition接口唯一的实现类;
  2. Condition是一种广义上的条件队列。他为线程提供了一种更为灵活的等待/通知模式,线程在调用await方法后执行挂起操作,直到线程等待的某个条件为真时才会被唤醒。Condition必须要配合锁一起使用,因为对共享状态变量的访问发生在多线程环境下。一个Condition的实例必须与一个Lock绑定,因此Condition一般都是作为Lock的内部实现。
  3. ConditionObject重要属性:
    3.1 Node firstWaiter;ConditionObject中的条件等待队列首节点
    3.2 Node lastWaiter; ConditionObject中的条件等待队列尾节点
    3.3 Node nextWaiter; 各等待节点间通过AQS中的Node#nextWaiter属性进行连接;

ConditionObject#await方法

public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException
    // 把当前线程封装成一个条件等待节点放入条件等待队列
    Node node = addConditionWaiter();
    //当前线程释放同步状态(锁)
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    //死循环检测条件等待节点是否进入CLH同步队列中。若没有进入,则挂起阻塞。
    //条件等待节点进入CLH同步队列的触发点在ConditionObject#signal、signalAll方法中
    while (!isOnSyncQueue(node)) {
        //线程挂起
        LockSupport.park(this);
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    //线程从上述while循环出来,说明当前节点已经进入CLH同步队列,说明有其他线程调用了signal或者signalAll
    //acquireQueued方法为一个自旋的过程,也就是说当前线程(Node)进入同步队列后,就会进入一个自旋的过程,每个节点都会自省地观察,当条件满足,获取到同步状态后,就可以从这个自旋过程中退出。
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null) // clean up if cancelled
    //清理条件队列中的不是在等待条件的节点
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}
  1. 将当前线程新建一个节点同时加入到条件队列中;
  2. 释放当前线程持有的同步状态;
  3. 循环检测该节点代表的线程释放出现在CLH同步队列中(收到signal信号之后就会在CLH队列中检测到)
  4. 如果不存在则一直挂起,否则参与竞争同步状态。

ConditionObject#signal方法、signalAll方法

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;
    //将原来在条件等待队列中的头节点加入到CLH同步队列中
    //enq就是入CLH队尾的方法,返回的结果p是当前节点node的前节点(在CLH同步队列中)
    Node p = enq(node);
    int ws = p.waitStatus;
    //使用CAS更改节点p状态的SIGNAL状态,以便节点p释放同步状态的时候可以唤醒node节点。
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        LockSupport.unpark(node.thread);
    return true;
}
  1. 判断当前线程是否拥有同步状态(锁),不拥有的话抛异常,因为获取同步状态(锁)为signal的前置条件。
  2. 将条件队列的首节点移出,通过AQS#enq方法加入到CLH同步队列的队尾。
  3. 通过CAS修改前置节点的waitStatus为SIGNAL,以便前置节点释放同步状态的时候可以唤醒node节点。所以可见signal方法并不会唤醒条件队列的头节点,唤醒的真正操作还是需要其他线程释放了同步状态(锁)资源,并且节点已经处于CLH队列中头节点的后一个节点(具体见AQS#release#unparkSuccessor方法)。
  4. 最后判断该节点的同步状态是否为Cancel,或者修改状态为Signal失败时,则直接调用LockSupport唤醒该节点的线程。

CyclicBarrier

  1. CyclicBarrier,一个同步辅助类,在API中是这么介绍的: 它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)。在涉及一组固定大小的线程的程序中,这些线程必须不时地互相等待,此时 CyclicBarrier 很有用。因为该 barrier 在释放等待线程后可以重用,所以称它为循环 的 barrier。
  2. 通俗点讲就是:让一组线程到达一个屏障时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。
  3. 实现原理主要是通过ReentrantLock和Condition进行实现的(这两者都是基于AQS框架实现的)。
  4. 每个线程调用CyclicBarrier#await方法时,其实是先通过ReentrantLock获取到同步状态(锁),然后再调用ConditionObject#await方法,这里就相当于把当前线程加入到条件等待队列中,并释放了同步状态(锁),挂起阻塞了。
  5. 当CyclicBarrier的信号量达到零时,就会在nextGeneration方法触发signalAll操作,把先前所有挂起阻塞的线程全部移入到CLH同步队列中,随后逐一竞争获取同步状态(锁)
  6. CyclicBarrier和CountDownLatch有两大方面的差别
    6.1 第一个是CyclicBarrier的信号量是循环使用的,CountDownLatch只能使用一次;
    6.2 CountDownLatch使用的是AQS共享模式的节点类型。所以在信号量达到零时,会把所有阻塞挂起的节点传播唤醒,不需要再额外在CLH同步队列中竞争获取同步状态。具体看AQS#doAcquireSharedInterruptibly方法。
    6.3 而CyclicBarrier使用的是ReentrantLock,所以他的AQS节点模式是独占的。所以在信号量达到零时,即使已经把所有节点都唤醒了(移入到CLH同步队列中),但也需要竞争获取到同步状态各线程才可以执行后续流程。
  7. 具体源码方法就不贴了,主要关注下dowait方法以及nextGeneration方法

ThreadPoolExecutor

  1. JUC包中的线程池也使用到了很多有关AQS的技术;
  2. 如Worker内部类继承了AQS,主要是在每个工作线程执行任务时加锁;
  3. ThreadPoolExecutor中的mainLock属性对象(ReentrantLock类),主要用于增加线程池时保证逻辑原子性
  4. ThreadPoolExecutor中的工作队列workQueue属性对象(BlockingQueue类),主要用于存放线程池需要执行的任务。
  5. BlockingQueue实现的原理也是通过ReentrantLock保证从队列增减元素的操作是互斥性、原子性的;还有就是BlockingQueue通过Condition来保证take方法和put方法能够做到队列为空时获取元素以及队列已满时添加元素会阻塞挂起线程。具体逻辑分析后面再单独阐述;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值