一、CyclicBarrier介绍
一个批次的线程都到达某种状态(调用CyclicBarrier.await方法)后,再执行另一个任务(由最后一个调用await方法的线程执行)。
同时,它是可以重复使用的,通过重置状态;而CountDownLatch是不能重复用的;
二、原理
创建CyclicBarrier实例时,指定线程数量和后续任务(也可以不指定这个任务),调用await()方法进行等待,await方法内部使用ReentrantLock,对等待中的线程数量减1,当等待中的线程数量为0时,则执行指定的后续任务,再执行阻塞线程后续的代码。
1、属性介绍
//记录栅栏的年代信息。当所有线程通行或者栅栏被重置的时候,会产生一个新的年代。
//因为CyclicBarrier可以重复使用,因此每一个Generation对象,就代表了一次CyclicBarrier的使用。
private static class Generation {
boolean broken = false;
}
//栅栏锁
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();
//等待中的线程数量。其值为0-parties之间。当栅栏被破坏或重置栅栏时,其值更新为parties。
private int count;
2、构造函数
//传入线程数量和指定的任务,初始化parties和count
public CyclicBarrier(int parties, Runnable barrierAction) {
if (parties <= 0) throw new IllegalArgumentException();
this.parties = parties;
this.count = parties;
this.barrierCommand = barrierAction;
}
public CyclicBarrier(int parties) { this(parties, null);}
3、await()方法:调用后线程进入等待,直到所有的线程都到达。
//线程进入等待状态,该方法还有超时参数版本。
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;
//加锁
lock.lock();
try {
final Generation g = generation;
//判断当前栅栏是否被重置了,如果被重置,则直接抛出异常
//创建Generation对象时,broken的值是false。当销毁栅栏(调用breakBarrier方法)时,broken被置为true。在一个批次的线程里,一个线程重置了栅栏,那么后续线程再调用await方法就会直接抛出异常。
if (g.broken)
throw new BrokenBarrierException();
//如果当前线程被中断了,那么重置栅栏,抛出异常。
//貌似,在多线程环境下,判断线程是否被中断,是一件很重要的事情(很多类里都这么判断)。
if (Thread.interrupted()) {
breakBarrier();
throw new InterruptedException();
}
//等待的线程数量减1,当最后一个线程到达(也就是等待的线程数量为0时),就执行指定的任务,然后通知所有等待中的线程恢复运行、重置count的值。
int index = --count;
if (index == 0) { // tripped
boolean ranAction = false;
try {
final Runnable command = barrierCommand;
if (command != null)
command.run();
ranAction = true;
//通知等待中的线程继续运行、重置count的值、创建新的Generation
nextGeneration();
return 0;
} finally {
//保证上面的任务出现异常(command.run()报错)时,也能销毁栅栏,
if (!ranAction)
breakBarrier();
}
}
// 到这里,说明count != 0,还有线程没有到达,所以先到达的线程需要阻塞等待。
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 {
//即使线程没有被中断,也对其进行中断
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();
}
}
//通知等待中的线程继续运行、重置count的值、创建新的Generation
private void nextGeneration() {
// signal completion of last generation
trip.signalAll();
// set up next generation
count = parties;
generation = new Generation();
}
//销毁栅栏(设置broken为true)、重置count、通知所有等待的线程继续运行
private void breakBarrier() {
generation.broken = true;
count = parties;
trip.signalAll();
}
4、isBroken():判断当前栅栏是否被销毁,broken的值会影响await()方法的执行
public boolean isBroken() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return generation.broken;
} finally {
lock.unlock();
}
}
5、reset():重置栅栏,触发的动作主要包括:销毁栅栏(broken=true)、通知所有阻塞的线程(在await方法里,在接收到notifyAll方法后,会判断broken的值,为true则抛出异常)、重置count,创建新的Generation。
public void reset() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
breakBarrier(); // break the current generation
nextGeneration(); // start a new generation
} finally {
lock.unlock();
}
}
三、示例
public class CyclicBarrierTest {
static class MyTask extends Thread{
CyclicBarrier barrier;
public MyTask(CyclicBarrier barrier) {
this.barrier = barrier;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "start wait");
try {
barrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "end wait");
}
}
public static void main(String[] args) {
CyclicBarrier barrier = new CyclicBarrier(5, () -> System.out.println(Thread.currentThread().getName() + ", 所有线程结束"));
for (int i = 0; i < 5; i++) {
new MyTask(barrier).start();
}
System.out.println("main线程结束");
}
}
运行结果:
Thread-0start wait
main线程结束
Thread-1start wait
Thread-3start wait
Thread-4start wait
Thread-2start wait
Thread-2, 所有线程结束
Thread-2end wait
Thread-4end wait
Thread-3end wait
Thread-1end wait
Thread-0end wait
结果分析:
1、主线程和新创建的5个线程没有等待关系,这点与CountDownLatch不同,在CountDownLatch使用中,一般是在主线程中调用await()方法进行等待,等所有线程都执行完后,再执行主线程。
2、所有线程都到达后,先执行指定的后续任务,再继续阻塞后的线程代码。