AbstractQueuedSynchronizer——JUC包的基石(下)
ConditionObject
public class ConditionObject implements Condition, java.io.Serializable
- AbstractQueuedSynchronizer中有一个内部类ConditionObject,该类实现了Condition接口。JUC包中,ConditionObject是Condition接口唯一的实现类;
- Condition是一种广义上的条件队列。他为线程提供了一种更为灵活的等待/通知模式,线程在调用await方法后执行挂起操作,直到线程等待的某个条件为真时才会被唤醒。Condition必须要配合锁一起使用,因为对共享状态变量的访问发生在多线程环境下。一个Condition的实例必须与一个Lock绑定,因此Condition一般都是作为Lock的内部实现。
- 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;
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)
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
- 将当前线程新建一个节点同时加入到条件队列中;
- 释放当前线程持有的同步状态;
- 循环检测该节点代表的线程释放出现在CLH同步队列中(收到signal信号之后就会在CLH队列中检测到)
- 如果不存在则一直挂起,否则参与竞争同步状态。
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;
Node p = enq(node);
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
- 判断当前线程是否拥有同步状态(锁),不拥有的话抛异常,因为获取同步状态(锁)为signal的前置条件。
- 将条件队列的首节点移出,通过AQS#enq方法加入到CLH同步队列的队尾。
- 通过CAS修改前置节点的waitStatus为SIGNAL,以便前置节点释放同步状态的时候可以唤醒node节点。所以可见signal方法并不会唤醒条件队列的头节点,唤醒的真正操作还是需要其他线程释放了同步状态(锁)资源,并且节点已经处于CLH队列中头节点的后一个节点(具体见AQS#release#unparkSuccessor方法)。
- 最后判断该节点的同步状态是否为Cancel,或者修改状态为Signal失败时,则直接调用LockSupport唤醒该节点的线程。
CyclicBarrier
- CyclicBarrier,一个同步辅助类,在API中是这么介绍的: 它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)。在涉及一组固定大小的线程的程序中,这些线程必须不时地互相等待,此时 CyclicBarrier 很有用。因为该 barrier 在释放等待线程后可以重用,所以称它为循环 的 barrier。
- 通俗点讲就是:让一组线程到达一个屏障时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。
- 实现原理主要是通过ReentrantLock和Condition进行实现的(这两者都是基于AQS框架实现的)。
- 每个线程调用CyclicBarrier#await方法时,其实是先通过ReentrantLock获取到同步状态(锁),然后再调用ConditionObject#await方法,这里就相当于把当前线程加入到条件等待队列中,并释放了同步状态(锁),挂起阻塞了。
- 当CyclicBarrier的信号量达到零时,就会在nextGeneration方法触发signalAll操作,把先前所有挂起阻塞的线程全部移入到CLH同步队列中,随后逐一竞争获取同步状态(锁)
- CyclicBarrier和CountDownLatch有两大方面的差别
6.1 第一个是CyclicBarrier的信号量是循环使用的,CountDownLatch只能使用一次;
6.2 CountDownLatch使用的是AQS共享模式的节点类型。所以在信号量达到零时,会把所有阻塞挂起的节点传播唤醒,不需要再额外在CLH同步队列中竞争获取同步状态。具体看AQS#doAcquireSharedInterruptibly方法。
6.3 而CyclicBarrier使用的是ReentrantLock,所以他的AQS节点模式是独占的。所以在信号量达到零时,即使已经把所有节点都唤醒了(移入到CLH同步队列中),但也需要竞争获取到同步状态各线程才可以执行后续流程。 - 具体源码方法就不贴了,主要关注下dowait方法以及nextGeneration方法
ThreadPoolExecutor
- JUC包中的线程池也使用到了很多有关AQS的技术;
- 如Worker内部类继承了AQS,主要是在每个工作线程执行任务时加锁;
- ThreadPoolExecutor中的mainLock属性对象(ReentrantLock类),主要用于增加线程池时保证逻辑原子性
- ThreadPoolExecutor中的工作队列workQueue属性对象(BlockingQueue类),主要用于存放线程池需要执行的任务。
- BlockingQueue实现的原理也是通过ReentrantLock保证从队列增减元素的操作是互斥性、原子性的;还有就是BlockingQueue通过Condition来保证take方法和put方法能够做到队列为空时获取元素以及队列已满时添加元素会阻塞挂起线程。具体逻辑分析后面再单独阐述;