术语
- 障栅动作: 实例化CyclicBarrier 的时候传进去的, 所有parties线程数都await 之后的要执行的操作.(实例化的时候可以传null的)
- 障栅点:
CyclicBarrier
调用await()
的代码位置 - 成员数(parties): 参与的线程的数目
- 索引 : 线程调用 await 时候, 剩余的成员数
- 世代 (generation): CyclicBarrier 会在实例化的时候开始一个
世代(generation)
, 每次 成员数降到 0 或者是 线程运行出现错误的时候, 会重新实例化一个世代
. 主要用于线程await
, 被唤醒之后, 如果发现当时await
保存的世代
跟当前的世代
不一样, 就会直接返回当时await
的 索引.
设计
内部使用一个 var trip = ReentrantLock.newCondition
来进行同步. 初始化 CyclicBarrier
的时候需要传一个成员数 parties
, 外部的线程每调用一次 CyclicBarrier
的 await()
方法, parties计数
就会减一, 减到 0 的时候, 就 执行 初始化 CyclicBarrier
的传进来的 障栅动作, 并唤醒所有等待的线程, 开启新的世代
.
源代码注释
如果障栅动作不依赖于挂起的成员, 那么任何线程都可以在其释放的时候执行动作.为了实现这个, 每个 await
的调用会返回到达障栅的索引(剩余的计数). 你可以选择哪个线程来执行障栅动作.
比如
if (barrier.await() == 0) {
// 打印日志表示这个迭代完成
}
同步障栅使用 要么全部要么都不
的损坏模型: 如果一个线程因为中断,异常, 超时导致离开了 障栅点, 所有其他在这个障栅点等待的线程也都会通过抛出 BrokenBarrierException
(或者 InterruptedException
如果他们几乎在同时被中断) 来退出.
内存一致性: 线程中在调用 await()
的动作 发生前于
(happen-before) 障栅动作. 而障栅动作优先发生于 barrier.await()
之后的动作.
等待/唤醒的逻辑
可以看到这个共用的 await 方法调用的是内部的dowait()
方法, 所以重点就是这个 doawait 方法
public int await() throws InterruptedException, BrokenBarrierException {
try {
return dowait(false, 0L);
} catch (TimeoutException toe) {
throw new Error(toe); // cannot happen
}
}
private int dowait(boolean timed, long nanos)
throws InterruptedException, BrokenBarrierException,
TimeoutException {
final ReentrantLock lock = this.lock;
//调用condition的await之前是要获取锁的, 跟object.wait()有异曲同工之妙
lock.lock();
try {
//保存当前的世代, 后面用于比较世代是否变了
//这里 generation 并没有声明 volitale, 因为已经加了锁
final Generation g = generation;
//如果当前世代已经坏了, 直接抛异常
if (g.broken)
throw new BrokenBarrierException();
//如果当前线程被中断了, 中断这个世代, 并抛异常
if (Thread.interrupted()) {
breakBarrier();
throw new InterruptedException();
}
//减少计数
int index = --count;
//当减到 0 的时候, 调用障栅动作
if (index == 0) { // tripped
Runnable command = barrierCommand;
if (command != null) {
try {
command.run();
} catch (Throwable ex) {
//出异常就中断世代, broken设为true,count重置为 parties,
// 同时 condition.signalAll()
breakBarrier();
throw ex;
}
}
//执行成功, 则开启下一个世代
//condition.signalAll(), count重置为 parties, 实例化新的 Generation
nextGeneration();
return 0;
}
// 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
if (g != generation)
return index;
//同个世代, 未中断, 且带有时间, 超时, 抛异常
if (timed && nanos <= 0L) {
breakBarrier();
throw new TimeoutException();
}
}
} finally {
lock.unlock();
}
}
感悟:
- 对于同一个条件的锁, 使用 Lock.newCondition 比 对象 的 monitor 粒度更小.
- condition 可以
await()
, 同时也具有await(time)
的带有超时的方法