前言
CyclicBarrier它是什么?一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点。类似于朋友之间联系要在中午聚个会,几个朋友全部到齐后才开始喝酒吃菜。它内部维护了一个计数值count,每当一个线程就绪时该计数值就减小一次,当count为0时表示所有线程就绪,此时唤醒所有线程。
源码
CyclicBarrier属性和构造器
public class CyclicBarrier {
// 互斥锁
private final ReentrantLock lock = new ReentrantLock();
// 条件等待
private final Condition trip = lock.newCondition();
// 参与者数目
private final int parties;
// 栅栏放开时的执行任务
private final Runnable barrierCommand;
// 用于表示栅栏是否放开或者重置
private Generation generation = new Generation();
// 在等待的参数者数目
private int count;
// 构造parties个参与者的栅栏
public CyclicBarrier(int parties) {
this(parties, null);
}
// 构造parties个参与者的栅栏,并且设置栅栏放开时要执行的任务
public CyclicBarrier(int parties, Runnable barrierAction) {
if (parties <= 0) throw new IllegalArgumentException();
this.parties = parties;
this.count = parties;
this.barrierCommand = barrierAction;
}
}
CyclicBarrier方法
1. await方法
在所有参与者都已经在此 栅栏上调用 await 方法之前或者超时,会一直等待
public int await() throws InterruptedException, BrokenBarrierException {
try {
return dowait(false, 0L);
} catch (TimeoutException toe) {
throw new Error(toe); // cannot happen
}
}
// 设置超时
public int await(long timeout, TimeUnit unit)
throws InterruptedException,
BrokenBarrierException,
TimeoutException {
return dowait(true, unit.toNanos(timeout));
}
private int dowait(boolean timed, long nanos)
throws InterruptedException, BrokenBarrierException,
TimeoutException {
// 给栅栏对象锁赋值
final ReentrantLock lock = this.lock;
// 加锁
lock.lock();
try {
final Generation g = generation;
// 栅栏状态
if (g.broken)
throw new BrokenBarrierException();
// 线程中断
if (Thread.interrupted()) {
// 将栅栏状态设置成损坏、重置剩余等待线程数以及唤醒等待的线程
breakBarrier();
throw new InterruptedException();
}
// 每调用一次await减少一次count数
int index = --count;
if (index == 0) { // tripped
// 所有参与者已到达栅栏位置
// 初始化执行任务状态,可见构造器设置的执行任务是由最后一个到达栅栏位置的线程来执行的
boolean ranAction = false;
try {
final Runnable command = barrierCommand;
if (command != null)
command.run();
ranAction = true;
// 重置栅栏并唤醒所有线程
nextGeneration();
return 0;
} finally {
if (!ranAction)
// command执行出错
// 任务执行出错将栅栏状态设置成损坏、重置剩余等待线程数以及唤醒等待的线程
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();
if (g != generation)
// 返回当前线程到达的顺序,数字越大表示到达的越早
return index;
if (timed && nanos <= 0L) {
// 超时 将栅栏状态设置成损坏、重置剩余等待线程数以及唤醒等待的线程
breakBarrier();
throw new TimeoutException();
}
}
} finally {
lock.unlock();
}
}
breakBarrier方法调用的signalAll,以及dowait方法中trip.await、trip.awaitNanos在本人这篇【JDK源码分析】通过源码深入分析AbstractQueuedSynchronizer博客有分析,此处不再赘述。
// 将栅栏状态设置成损坏、重置剩余等待线程数以及唤醒等待的线程
private void breakBarrier() {
generation.broken = true;
count = parties;
// 唤醒所有线程
trip.signalAll();
}
// 重置栅栏,下次还可以再使用
private void nextGeneration() {
// 唤醒所有线程
trip.signalAll();
count = parties;
generation = new Generation();
}
2. reset方法
将栅栏重置为其初始状态并开启一个新的栅栏
public void reset() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
breakBarrier(); // break the current generation
nextGeneration(); // start a new generation
} finally {
lock.unlock();
}
}
CyclicBarrier的源码分析到此结束
用法
如下面,张锅和张嫂约起去火锅店吃火锅,二个人到齐后才叫老板上菜,下面上示例:
final CyclicBarrier cyclicBarrier = new CyclicBarrier(2,
() -> System.out.println(Thread.currentThread().getName() + "喊:老板,我们人到齐了,把菜上起!"));
new Thread(() ->{
System.out.println("张锅在去火锅店的路上...");
try {
Thread.sleep(System.currentTimeMillis() % 1000);
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}, "张锅").start();
new Thread(() ->{
System.out.println("张嫂在去火锅店的路上...");
try {
Thread.sleep(System.currentTimeMillis() % 1000);
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}, "张嫂").start();
打印结果
张锅在去火锅店的路上...
张嫂在去火锅店的路上...
张锅喊:老板,我们人到齐了,把菜上起!
总结
当遇到多条线程需要相互等待时,使用CyclicBarrier工具是一个比较好的选择。