概述
CyclicBarrier是一个同步工具类,可以翻译成循环屏障,也叫障碍器或同步屏障。
CyclicBarrier内部有一个计数器count,调用障碍器的await方法会使计数器count的值减一,当计数器count的值为0时,表明调用了await方法线程已经达到了设置的数量。
当障碍器的屏障被打破后,会重置计数器,因此叫做循环屏障。
比较CountDownLatch和CyclicBarrier:
- CountDownLatch的作用其实就是一个计数器,当阻塞的线程数达到CountDownLatch设置的临界值后,CountDownLatch将会唤醒阻塞的线程,并且后面将失效不再阻塞线程,因此CountDownLatch也可以理解为一次性的障碍器
- 相比较CountDownLatch , CyclicBarrier可以设置Runnable类型的条件线程barrierCommand(屏障打破时会触发的执行),并且CyclicBarrier的是循环屏障,CyclicBarrier只要内部不发生异常,是可以通过重置计数器来重复使用的。
原理
障碍器内部有一个ReentrantLock变量lock(显式锁),还有通过该显式锁lock获得的Condition变量trip。在线程里调用障碍器await方法,而在await方法内部调用了dowait方法(dowait方法使用了显式锁变量lock),在dowait方法内部会根据计数器count判断,如果count不等于0,将会调用Condition变量trip的await方法,也就是说实际上障碍器的await方法是通过Condition变量trip的await()方法阻塞了所有的进行到这里的线程, 每个线程执行await方法都会令计数器count减一,当count值为0时,然后会调用Condition变量trip的signalAll方法,唤醒所有阻塞的线程。
作用
- 设置一个屏障(也可以叫同步点),当某一个线程到达屏障后会阻塞该线程,只有当到达屏障的线程数达到临界值parties后,那些在屏障处被阻塞的线程才被唤醒继续执行。
- 可以在屏障处设置一个待执行的线程A,当所有线程到达屏障时,会执行线程A,然后打开屏障让哪些被阻塞的线程继续执行。这里容易有一个误解就是,并不是要等到线程A执行结束后,被阻塞的线程才继续执行,如果线程A中调用了wait()、yield方法,此时被阻塞的线程可以不必等到线程A执行完毕就能继续执行,而如果线程A调用了sleep方法,被阻塞的线程仍然需要等到线程A执行完毕后才能继续执行。
CyclicBarrier的内部变量
//该内部类用于表明当前循环屏障的状态,当broken为true时表示障碍器发生了异常
private static class Generation {
boolean broken = false;
}
//CyclicBarrier内部的显示锁
private final ReentrantLock lock = new ReentrantLock();
//通过上面的显式锁得到的Condition变量,障碍器能够阻塞和唤醒多个线程完全得益于这个Condition
private final Condition trip = lock.newCondition();
//临界值,当障碍器阻塞的线程数等于parties时即count=0,障碍器将会通过trip唤醒目前所有阻塞的线程
private final int parties;
//条件线程,当屏障被打破时,在障碍器通过trip唤醒所有正被阻塞的的线程之前,执行该线程,这个线程可以充当一个主线程,那些被阻塞的线程可以充当子线程,即可以实现当所有子线程都达到屏障时调用主线程的作用
private final Runnable barrierCommand;
//内部类Generation变量表示当前循环屏障CyclicBarrier的状态
private Generation generation = new Generation();
//计数器,用于计算还剩多少个线程还没有达到屏障处,初始值应该等于临界值parties
private int count;
构造函数源码
//这个构造函数设置了条件线程barrierAction
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);
}
CyclicBarrier循环屏障实现阻塞和唤醒线程的关键源码
1.await方法源码
//CyclicBarrier内部定义的无参的await方法,可以看出在内部调用的dowait方法才是关键
public int await() throws InterruptedException, BrokenBarrierException {
try {
return dowait(false, 0L);
} catch (TimeoutException toe) {
throw new Error(toe); // cannot happen
}
}
//CyclicBarrier内部定义的有参的await方法,这两个参数的作用是表示线程到达屏障后需要等待的时间,如果不设置时间当前线程无论如何也需要等到所有的线程都达到屏障后才能继续执行,而设置时间后,当等待的时间等于设置的时间后,无论还有没有线程到达屏障当前线程都将继续执行
public int await(long timeout, TimeUnit unit)
throws InterruptedException,
BrokenBarrierException,
TimeoutException {
return dowait(true, unit.toNanos(timeout));
}
2.dowait方法源码,这个方法中实现了障碍器阻塞线程和唤醒线程的功能
//timed:表示是否设置了等待时间
//nanos等待的时间(纳秒)
private int dowait(boolean timed, long nanos)
throws InterruptedException, BrokenBarrierException,
TimeoutException {
final ReentrantLock lock = this.lock;
//使用CyclicBarrier定义的显示锁,加锁避免并发问题
lock.lock();
try {
//当前循环屏障的状态
final Generation g = generation;
//如果为true,表示障碍器之前发生了异常,抛出异常BrokenBarrierException
if (g.broken)
throw new BrokenBarrierException();
//当前线程是否被中断
if (Thread.interrupted()) {
breakBarrier();//该方法会重置计数值count为parties,并且唤醒所有被阻塞的线程,并改变状态Generation
throw new InterruptedException();
}
//屏障计数器减一
int index = --count;
//如果index等于0 ,达到屏障的线程的数量等于最开始设置的数量parties
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 {
if (!ranAction)//如果ranAction为true,表示上面的代码没有顺利执行结束,表示障碍器发生了异常,调用breakBarrier重置计数器,并设置generation.broken=true表示当前的状态
breakBarrier();
}
}
// 当计数器为零调用了Condition的唤醒方法、或者broken为true、或者线程中断、或者等待超时时跳出异常
for (;;) {
try {
//阻塞当前线程,如果timed为false表示没有设置等待的时间
if (!timed)
//不限时阻塞线程,只有当调用唤醒方法后才会继续执行
trip.await();
else if (nanos > 0L)
//等待nanos毫秒
nanos = trip.awaitNanos(nanos);
} catch (InterruptedException ie) {
//调用await方法如果发生异常,并且此时CyclicBarrier还没有调用nextGeneration()方法重置计数器和generation
if (g == generation && ! g.broken) {
breakBarrier();//该方法会唤醒所有阻塞的线程,并且重置计数器,而且设置generation.broken = true表示障碍器发生了异常。
throw ie;
} else {
//中断当前线程
Thread.currentThread().interrupt();
}
}
//g.broken为true,表示障碍器发生了异常,抛出异常
if (g.broken)
throw new BrokenBarrierException();
//index=0的唤醒操作顺利执行完了,所以通过nextGeneration()方法更新了generation,而由于generation是线程中的共享变量,所以当前线程此时 g!=generation
if (g != generation)
return index;
//如果timed为true表示设置了线程阻塞的时间,然后时间nanos却小于等于0,
if (timed && nanos <= 0L) {
breakBarrier();//此时重置计数器,并且设置generation.broken=true表示障碍器发生异常
throw new TimeoutException();
}
}
} finally {
lock.unlock();
}
}
//唤醒所有线程,重置计数器count,重新生成generation
private void nextGeneration() {
trip.signalAll();
count = parties;
generation = new Generation();
}
//设置generation.broken=true表示障碍器发生的异常,重置计数器count,唤醒所有阻塞的线程
private void breakBarrier() {
generation.broken = true;
count = parties;
trip.signalAll();
}
例子说明:
下面的例子可以说明循环障碍CyclicBarrier的使用方式:
public class TestCyclicBarrier {
public static void main(String[] args) {
/**
*
* 为了适应一种新的设计需求,比如一个大型的任务,常常需要分配好多子任务去执行,
* 只有当所有子任务都通知主任务执行的时候,才能执行主任务,这时候,就可以选择障碍器了。
*/
// 创建障碍器,并设置MainTask为所有定数量的线程都达到障碍点时候所要执行的任务(Runnable)
CyclicBarrier cb = new CyclicBarrier(3, new MainTask());
new SubTask("A", cb, 1).start();
new SubTask("B", cb, 2).start();
new SubTask("C", cb, 3).start();
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
new SubTask("D", cb, 4).start();
new SubTask("E", cb, 5).start();
new SubTask("F", cb, 6).start();
}
}
/**
* 主任务
*/
class MainTask implements Runnable {
public void run() {
System.out.println("-----------在收到所有的子任务开始执行的通知后,主任务执行了!");
}
}
/**
* 子任务
*/
class SubTask extends Thread {
private String name;
private CyclicBarrier cb;
private int n;
SubTask(String name, CyclicBarrier cb, int n) {
this.name = name;
this.cb = cb;
this.n = n;
}
public void run() {
System.out.println("[子任务" + name + "]开始执行了!");
try {
// 模拟耗时的任务
Thread.sleep(1000 * n);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
System.out.println("[子任务" + name + "]暂停,并通知障碍器主任务该执行了!");
try {
//无限时等待
cb.await();
//设置等待时间,等待时间到了之后,就算障碍器没有打开,主线程还没有执行,当前线程依然继续执行
// cb.await(1000,TimeUnit.MILLISECONDS);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("主任务执行完毕后,[子任务" + name + "]继续执行剩下的部分!");
}
}
执行的结果为:
[子任务A]开始执行了!
[子任务C]开始执行了!
[子任务B]开始执行了!
[子任务A]暂停,并通知障碍器主任务该执行了!
[子任务B]暂停,并通知障碍器主任务该执行了!
[子任务C]暂停,并通知障碍器主任务该执行了!
-----------在收到所有的子任务开始执行的通知后,主任务执行了!
主任务执行完毕后,[子任务C]继续执行剩下的部分!
主任务执行完毕后,[子任务A]继续执行剩下的部分!
主任务执行完毕后,[子任务B]继续执行剩下的部分!
[子任务D]开始执行了!
[子任务E]开始执行了!
[子任务F]开始执行了!
[子任务D]暂停,并通知障碍器主任务该执行了!
[子任务E]暂停,并通知障碍器主任务该执行了!
[子任务F]暂停,并通知障碍器主任务该执行了!
-----------在收到所有的子任务开始执行的通知后,主任务执行了!
主任务执行完毕后,[子任务F]继续执行剩下的部分!
主任务执行完毕后,[子任务D]继续执行剩下的部分!
主任务执行完毕后,[子任务E]继续执行剩下的部分!