CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。CyclicBarrier默认的构造方法是CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用await方法告诉CyclicBarrier我已经到达了屏障,然后当前线程被阻塞。
文章目录
CyclicBarrier简介
CyclicBarrier也是一种多线程并发控制的实用工具,和CountDownLatch一样具有等待计数的功能,但是相比于CountDownLatch功能更加强大。
CyclicBarrier的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续运行。
为了理解CyclicBarrier,这里举一个通俗的例子。开运动会时,会有跑步这一项运动,我们来模拟下运动员入场时的情况,假设有6条跑道,在比赛开始时,就需要6个运动员在比赛开始的时候都站在起点了,裁判员吹哨后才能开始跑步。跑道起点就相当于“barrier”,是临界点,而这6个运动员就类比成线程的话,就是这6个线程都必须到达指定点了,意味着凑齐了一波,然后才能继续执行,否则每个线程都得阻塞等待,直至凑齐一波即可。cyclic是循环的意思,也就是说CyclicBarrier当多个线程凑齐了一波之后,仍然有效,可以继续凑齐下一波。CyclicBarrier的执行示意图如下:
当多个线程都达到了指定点后,才能继续往下继续执行。这就有点像报数的感觉,假设6个线程就相当于6个运动员,到赛道起点时会报数进行统计,如果刚好是6的话,这一波就凑齐了,才能往下执行。CyclicBarrier在使用一次后,下面依然有效,可以继续当做计数器使用,这是与CountDownLatch的区别之一。这里的6个线程,也就是计数器的初始值6,是通过CyclicBarrier的构造方法传入的。
CyclicBarrier方法
CyclicBarrier方法
下面来看下CyclicBarrier的主要方法:
//等到所有的线程都到达指定的临界点
await() throws InterruptedException, BrokenBarrierException
//与上面的await方法功能基本一致,只不过这里有超时限制,阻塞等待直至到达超时时间为止
await(long timeout, TimeUnit unit) throws InterruptedException,
BrokenBarrierException, TimeoutException
//获取当前有多少个线程阻塞等待在临界点上
int getNumberWaiting()
//用于查询阻塞等待的线程是否被中断
boolean isBroken()
//将屏障重置为初始状态。如果当前有线程正在临界点等待的话,将抛出BrokenBarrierException。
void reset()
CyclicBarrier实例
package cn.wideth.util;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MyCyclicBarrier {
//指定必须有6个运动员到达才行
private static CyclicBarrier barrier = new CyclicBarrier(6, () ->
System.out.println("所有运动员入场,裁判员一声令下!!!!!")
);
public static void main(String[] args) {
System.out.println("运动员进场中...");
ExecutorService service = Executors.newFixedThreadPool(6);
for (int i = 0; i < 6; i++) {
service.execute(() -> {
try {
System.out.println(Thread.currentThread().getName() + " 运动员,进场");
barrier.await();
System.out.println(Thread.currentThread().getName() + " 运动员出发");
} catch (Exception e) {
e.printStackTrace();
}
});
}
}
}
运行结果
代码分析:
当某个线程调用了await方法之后,就会进入等待状态,并将计数器-1,直到所有线程调用await方法使计数器为0,才可以继续执行,由于计数器可以重复使用,所以我们又叫它循环屏障。
CyclicBarrier默认的构造方法是CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用await方法告诉CyclicBarrier我已经到达了屏障,然后当前线程被阻塞。
CyclicBarrier和CountDownLatch的区别
- CountDownLatch的计数器只能使用一次。而CyclicBarrier的计数器可以使用reset()方法重置。所以CyclicBarrier能处理更为复杂的业务场景,比如如果计算发生错误,可以重置计数器,并让线程们重新执行一次。
- CountDownLatch主要用于实现一个或n个线程需要等待其他线程完成某项操作之后,才能继续往下执行,描述的是一个或n个线程等待其他线程的关系,而CyclicBarrier是多个线程相互等待,知道满足条件以后再一起往下执行。描述的是多个线程相互等待的场景
- CyclicBarrier还提供其他有用的方法,比如getNumberWaiting方法可以获得CyclicBarrier阻塞的线程数量。isBroken方法用来知道阻塞的线程是否被中断。
CyclicBarrier类的源码分析
内部实际上通过ReentrantLock控制线程以及通过ReentrantLock得到Condition接口,通过condition控制线程。CyclicBarrier的使用则是通过new CyclicBarrier(10)表示可以拦截10个线程,然后在多线程中通过await()方法,会通过Condition接口让线程阻塞,当很多线程调用await()使CyclicBarrier实例中的count=0时则会被condition通过notifyAll唤醒所有线程,然后拦截器重新生成一个Generation ,并count被重新赋值为parties然后CyclicBarrier就可以被重复利用了。
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class CyclicBarrier {
private final ReentrantLock lock = new ReentrantLock(); // 底层是可重入的非公平锁
private final Condition trip = lock.newCondition(); // condition接口
private final int parties; // 总数,构造时传入,目的时做屏障器的计数(被设计成常量也是出于此,目的就是为了重复利用屏障器)
private int count; // 计数(count则会根据调用await减一,为0后会被重新赋值为parties)
private final Runnable barrierCommand; // runable的线程任务
private Generation generation = new Generation(); // 存在目的是循环使用CyclicBarrier,相当于每一次循环这个对象就会变具体看 nextGeneration()
/**
* 构造方法,传入屏障器计数和Runnable接口的任务
*/
public CyclicBarrier(int parties, Runnable barrierAction) {
if (parties <= 0) throw new IllegalArgumentException(); // 如果屏障器的计数非正数则不合法,抛异常
this.parties = parties; // 总数
this.count = parties; // 相当于信号量,每调用一次await就会被减1,为0重新赋值为parties
this.barrierCommand = barrierAction;// 当计数为0时会执行这个Runnable任务
}
/**
* 构造方法
*/
public CyclicBarrier(int parties) {
this(parties, null);
}
/*静态内部类*/
private static class Generation {
Generation() {} // 构造方法
boolean broken; // 初始化会是false,如果为true表示CyclicBarrier不再进行循环
}
/**
* 唤醒阻塞线程,进行下一个拦截循环
*/
private void nextGeneration() {
trip.signalAll(); // trip时可重入锁得到的condition接口,调用这个方法会唤醒所有正在等待的线程
count = parties; // 唤醒了所有的线程,因此count计数会重新赋值为parties,相当于重用屏障器
generation = new Generation(); // 生成新的
}
/**
* 暂停拦截器作用
*/
private void breakBarrier() {
generation.broken = true; // 设置为false
count = parties; // count重新赋值为parties,再次使用时就是新的循环
trip.signalAll(); // 唤醒所有被阻塞的线程
}
/**
* 拦截线程的方法,被await调用传入的是false,0
*/
private int dowait(boolean timed, long nanos) throws InterruptedException, BrokenBarrierException, TimeoutException {
final ReentrantLock lock = this.lock; //可重入锁
lock.lock(); // 加锁
try {
final Generation g = generation; // 得到当前拦截器的用于标识的Generation
if (g.broken) // 如果为true,说明暂停了拦截(调用了breakBarrier方法)导致
throw new BrokenBarrierException();//因为已经被叫停拦截器了所以抛异常
if (Thread.interrupted()) { // 当前线程是否被中断
breakBarrier(); // 暂停拦截器
throw new InterruptedException(); // 抛出被中断异常
}
// 计算剩余值
int index = --count; // 如果执行到这里,说明拦截器正常使用,因此计数减一,相当于使用了一个信号一个意思,而parties则是总信号量
// 如果剩余值为0执行任务
if (index == 0) { // 当前线程使用了一个计数后(相当于使用了一个信号量,判断信号量是否被使用完了)如果为0则进入
Runnable command = barrierCommand; // 任务
if (command != null) { // 如果任务不为空
try {
command.run(); // 执行任务
} catch (Throwable ex) {
breakBarrier(); //异常则暂停拦截器并且下面throw异常
throw ex;
}
}
nextGeneration(); // 生成下一个循环拦截
return 0;//
}
// 如果剩余值不为0,则将当前线程阻塞
/**
* 自旋方式降低cpu上下文切换的可能
*/
for (;;) {
//下面的try似乎不满足条件所以看后面
try {
if (!timed) // 默认传入的是false,!false=true,所以会让线程阻塞
trip.await(); // 通过condition接口阻塞当前线程
else if (nanos > 0L)// 默认是0所以不大于
nanos = trip.awaitNanos(nanos); // 阻塞nanos纳秒,实际默认值是0纳秒
} 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();//解锁
}
}
// 获得parties的值(拦截器能够拦截线程的总数)
public int getParties() {
return parties;
}
/**
* 拦截线程,等待通知.最常用方法,本质调用dowait方法
*/
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));
}
/**
* 返回generation.broken的值,目的是根据true/false判断是否拦截器是否继续使用,false则暂停使用
*/
public boolean isBroken() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return generation.broken;
} finally {
lock.unlock();
}
}
/**
* 重置拦截器
*/
public void reset() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
breakBarrier(); // 暂停拦截器
nextGeneration(); // 生成下一个循环
} finally {
lock.unlock();
}
}
/**
* 获取正在等待的线程数
*/
public int getNumberWaiting() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return parties - count; // 相当于总信号量-剩余信号量=正在执行的线程数(在CyclicBarrier则是自旋的线程数)
} finally {
lock.unlock();
}
}
}
本文小结
本文详细介绍了CyclicBarrier循环栅栏的基本概念,应用场景,常用方法以及源码分析。