cyclic 循环的 barrier 栅栏。
latch vs barrier
门闩,可以加一道,两道,三道。
而栅栏只有一道,并且把所有的线程都拦住了。
A synchronization aid that allows a set of threads to all wait for each other to reach a common barrier point.
The barrier is called cyclic because it can be re-used after the waiting threads are released.
栅栏可以看成是拦羊
成员变量:
parties: 要拦住几只羊?
count:已经拦住了几只羊?
barrierCommand: 拦住了这么多羊之后,是通知农场主还是农场主夫人?
generation:这是第几次拦羊了?--不是,是这个栅栏是不是没用了,损坏了,只有一个false和一个true
generation里面只有一个bool值 broken
CyclicBarrier内部逻辑:
1. 创建栅栏,设置好parties
2. 在各个线程中设置barries.dowait()
3. 每调用一次dowait,barries.count--
3.1 如果count=0,则进入下一个generation,通知所有正在park的线程(从condition等待队列转入syc同步队列)
3.2 如果count!=0, 则trip.await(实际调用的是ConditionObject.await方法),将node从sync队列转入condition的等待队列,然后park线程。
4. 异常处理:
4.1 如果拿到锁之后,g.broken= true了,那么也就意味着这个barrie损坏了,那么抛出异常,释放锁
4.2 如果当前线程被中断了,或者操作中有异常,breakBarrier,打破栅栏,通知所有等待的线程
主要方法:dowait
private int dowait(boolean timed, long nanos)
throws InterruptedException, BrokenBarrierException,
TimeoutException {
final ReentrantLock lock = this.lock;
lock.lock();
try {
final Generation g = generation;
//一开始进来,barrier就是损坏的,直接unlock出去
if (g.broken)
throw new BrokenBarrierException();
//如果线程被中断,那就打破栅栏,并通知其他所有的小伙伴------这里一直想不通,为什么要把他们绑定在一起?
if (Thread.interrupted()) {
breakBarrier();
throw new InterruptedException();
}
int index = --count;
if (index == 0) { // tripped
boolean ranAction = false;
try {
//有两种情况会有返回值,都是正常情况,第一种就是正好是最后一个线程,然后generation顺利进入下一代,返回0
final Runnable command = barrierCommand;
if (command != null)
command.run();
ranAction = true;
nextGeneration();
return 0;
} finally {
if (!ranAction)
breakBarrier();
}
}
// loop until tripped, broken, interrupted, or timed out
for (;;) {
try {
if (!timed)
trip.await();
else if (nanos > 0L)
nanos = trip.awaitNanos(nanos);
} catch (InterruptedException ie) {
if (g == generation && ! g.broken) {
breakBarrier();
throw ie;
} else {
// We're about to finish waiting even if we had not
// been interrupted, so this interrupt is deemed to
// "belong" to subsequent execution.
Thread.currentThread().interrupt();
}
}
if (g.broken)
throw new BrokenBarrierException();
//这是第二种正常情况,也就是被通知的其他小伙伴,醒来之后,发现已经换代了,返回当前的index
//但是这里也有一个问题,线程park了之后,index还是保留的原来的那个index呢?还是最新的呢?按道理来说应该是原来的,那么原来的又有什么意义呢?
if (g != generation)
return index;
//超时打破
if (timed && nanos <= 0L) {
breakBarrier();
throw new TimeoutException();
}
}
} finally {
lock.unlock();
}
}
这里trip.await的排队都是用的Condition的排队等。那么condition是怎么处理的呢?
首先Condition实现了AQS,而AQS中Condition的子类,ConditionObject。
ConditionObject虽然沿用了Node来作为condition的队列,但是这个队列跟aqs的队列并不是同一个。
沿用网上的说法,aqs的叫同步等待队列,condition的叫条件等待队列。
conditionObject有这么多方法,但是实际实用的是这两个:
1. addConditionWaiter 条件等待队列增加节点
2. doSignal 从条件队列移除,移至同步等待队列
trip.await的时候:
1. 加入等待队列,释放掉aqs的锁,唤醒同步队列的next
2. 循环中判断是否在同步队列(这里涉及到signal的操作,signal之后,会把node从等待队列移动到同步队列)
3. 如果已经在同步队列,那么重新取锁
4. 如果等待队列还有next,那么清空等待队列中非condition的节点。
5. 中断处理
breakBarrier和nextGeneration中有调用signalAll
singnalAll
1. 从first开始,先判断是否取消
2. 将node加入sync队列(enq方法),并将condition等待队列的节点一个一个转换成Signal的节点,也就是waitStatus改成Signal
3. unpark线程
1. 为什么一个线程中断了,就必须打破barrier呢?
难道是因为它持有锁?释放锁不就可以了嘛?
2. breakBarrier 和 nextGeneration有什么区别?
breakBarrier是打破栅栏,直接损坏了,也就是打破了Cyclic这个循环的属性,以后不能再用了。
nextGeneration是进入下一个代,也就是可以循环使用的。
在doWait方法的一开始就判断了g.broken,如果是true,直接抛出异常,也就是说,如果barrier损坏了,那就没有等待这一说了。
而在死循环下,也就是await之后,先是判断了g.broken,这种情况就是异常后signal的情况,直接释放。
然后再判断了g = generation,也就是看是不是当前代,如果不是,直接返回index。
3. g!=generation时,index返回有意义嘛?
感觉没有意义。
4. cyclicBarrier 有先后顺序嘛?
有,但是不明显。除了最后一个先走,其他的羊 栅栏关上的时候,谁先来,那么栅栏打开的时候,谁就最先走。
栅栏关上,所有的羊都挂在等待队列里。
羊的数量达到了。
栅栏打开,先是最后一只羊开始跑出圈,然后其他拦住的羊分别从第一只羊开始,从等待队列里,移到同步队列里。
再依次跑出圈。
所以,拼团这种活动,最后一个最快,其次是第一个,再就是按照顺序一个一个来。
5. 为什么wait都要放在死循环里?
因为唤醒之后,如果没有在死循环里,那么方法就跳出去了。
不仅需要在死循环了,而且还要对唤醒后的业务重新操作。