一、介绍
从名字上来看CyclicBarrier,就是代表循环屏障。CyclicBarrier一般被称为栅栏。
Barrier屏障:让一个或多个线程达到一个屏障点,会被阻塞。屏障点会有一个数值,当达到一个线程阻塞在屏障点时,就会对屏障点的数值进行-1操作,当屏障点数值减为0时,屏障就会打开,唤醒所有阻塞在屏障点的线程。在释放屏障点之后,可以先执行一个任务,再让所有阻塞被唤醒的线程继续之后后续任务。
Cyclic循环:所有线程被释放后,屏障点的数值可以再次被重置。
CyclicBarrier是一种同步机制,允许一组线程互相等待。现成的达到屏障点其实是基于await方法在屏障点阻塞。
CyclicBarrier并没有基于AQS实现,他是基于ReentrantLock锁的机制去实现了对屏障点–,以及线程挂起的操作。(CountDownLatch本身是基于AQS,对state进行release操作后,可以-1)
CyclicBarrier没来一个线程执行await,都会对屏障数值进行-1操作,每次-1后,立即查看数值是否为0,如果为0,直接唤醒所有的互相等待线程。
CyclicBarrier对比CountDownLatch区别
- 底层实现不同。CyclicBarrier基于ReentrantLock做的。CountDownLatch直接基于AQS做的。
- 应用场景不同。CountDownLatch的计数器只能使用一次。而CyclicBarrier在计数器达到0之后,可以重置计数器。CyclicBarrier可以实现相比CountDownLatch更复杂的业务,执行业务时出现了错误,可以重置CyclicBarrier计数器,再次执行一次。
- CyclicBarrier还提供了很多其他的功能:
- 可以获取到阻塞的现成有多少
- 在线程互相等待时,如果有等待的线程中断,可以抛出异常,避免无限等待的问题。
- CountDownLatch一般是让主线程等待,让子线程对计数器–。CyclicBarrier更多的让子线程也一起计数和等待,等待的线程达到数值后,再统一唤醒
CyclicBarrier:多个线程互相等待,直到到达同一个同步点,再一次执行。
二、应用
出国旅游:
导游小姐姐需要等待所有乘客都到位后,发送护照,签证等等文件,再一起出发
比如Tom,Jack,Rose三个人组个团出门旅游
在构建CyclicBarrier可以指定barrierAction,可以选择性指定,如果指定了,那么会在barrier归0后,优先执行barrierAction任务,然后再去唤醒所有阻塞挂起的线程,并行去处理后续任务。
所有互相等待的线程,可以指定等待时间,并且在等待的过程中,如果有线程中断,所有互相的等待的线程都会被唤醒。
如果在等待期间,有线程中断了,唤醒所有线程后,CyclicBarrier无法继续使用。
如果线程中断后,需要继续使用当前的CyclicBarrier,需要调用reset方法,让CyclicBarrier重置。
如果CyclicBarrier的屏障数值到达0之后,他默认会重置屏障数值,CyclicBarrier在没有线程中断时,是可以重复使用的。
public static void main(String[] args) throws InterruptedException {
CyclicBarrier barrier = new CyclicBarrier(3,() -> {
System.out.println("等到各位大佬都到位之后,分发护照和签证等内容!");
});
new Thread(() -> {
System.out.println("Tom到位!!!");
try {
barrier.await();
} catch (Exception e) {
System.out.println("悲剧,人没到齐!");
return;
}
System.out.println("Tom出发!!!");
}).start();
Thread.sleep(100);
new Thread(() -> {
System.out.println("Jack到位!!!");
try {
barrier.await();
} catch (Exception e) {
System.out.println("悲剧,人没到齐!");
return;
}
System.out.println("Jack出发!!!");
}).start();
Thread.sleep(100);
new Thread(() -> {
System.out.println("Rose到位!!!");
try {
barrier.await();
} catch (Exception e) {
System.out.println("悲剧,人没到齐!");
return;
}
System.out.println("Rose出发!!!");
}).start();
/*
tom到位,jack到位,rose到位
导游发签证
tom出发,jack出发,rose出发
*/
}
三、 源码分析
分成两块内容去查看,首先查看CyclicBarrier的一些核心属性,然后再查看CyclicBarrier的核心方法
3.1 CyclicBarrier的核心属性
public class CyclicBarrier {
// 这个静态内部类是用来标记是否中断的
private static class Generation {
boolean broken = false;
}
/** CyclicBarrier是基于ReentrantLock实现的互斥操作,以及计数原子性操作 */
private final ReentrantLock lock = new ReentrantLock();
/** 基于当前的Condition实现线程的挂起和唤醒 */
private final Condition trip = lock.newCondition();
/** 记录有参构造传入的屏障数值,不会对这个数值做操作 */
private final int parties;
/** 当屏障数值达到0之后,优先执行当前任务 */
private final Runnable barrierCommand;
/** 初始化默认的Generation,用来标记线程中断情况 */
private Generation generation = new Generation();
/** 每来一个线程等待,就对count进行-- */
private int count;
}
3.2 CyclicBarrier的有参构造
掌握构建CyclicBarrier之后,内部属性的情况
// 这个是CyclicBarrier的有参构造
// 在内部传入了parties,屏障点的数值
// 还传入了barrierAction,屏障点的数值达到0,优先执行barrierAction任务
public CyclicBarrier(int parties, Runnable barrierAction) {
// 健壮性判
if (parties <= 0) throw new IllegalArgumentException();
// 当前类中的属性parties是保存屏障点数值的
this.parties = parties;
// 将parties赋值给属性count,每来一个线程,继续count做-1操作。
this.count = parties;
// 优先执行的任务
this.barrierCommand = barrierAction;
}
3.3 CyclicBarrier中的await方法
在CyclicBarrier中,提供了2个await方法
- 第一个是无参的方式,线程要死等,直屏障点数值为0,或者有线程中断
- 第二个是有参方式,传入等待的时间,要么时间到位了,要不就是直屏障点数值为0,或者有线程中断
无论是哪种await方法,核心都在于内部调用的dowait方法
dowait方法主要包含了线程互相等待的逻辑,以及屏障点数值到达0之后的操作
// 包含了线程互相等到的逻辑,以及屏障点数值到达0后的操作
private int dowait(boolean timed, long nanos)throws
// 当前新编程中断,抛出这个异常
InterruptedException,
// 其他线程中断,当前线程抛出这个异常
BrokenBarrierException,
// await时间到位,抛出这个异常
TimeoutException {
// 加锁
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 拿到Generation对象的引用
final Generation g = generation;
// 判断下线程中断了么?如果中断了,直接抛出异常
if (g.broken)
throw new BrokenBarrierException();
// 当前线程中断了么?
if (Thread.interrupted()) {
// 做了三个实现,
// 设置broken为true,将count重置,唤醒其他等待的线程
breakBarrier();
// 抛出异常
throw new InterruptedException();
}
// 屏障点做--
int index = --count;
// 如果屏障点为0,打开屏障啦!!
if (index == 0) {
// 标记
boolean ranAction = false;
try {
// 拿到有参构造中传递的任务
final Runnable command = barrierCommand;
// 任务不为null,优先执行当前任务
if (command != null)
command.run();
// 上述任务执行没问题,标记位设置为true
ranAction = true;
// 执行nextGeneration
// 唤醒所有线程,重置count,重置generation
nextGeneration();
return 0;
} finally {
// 如果优先执行的任务出了问题i,就直接抛出异常
if (!ranAction)
breakBarrier();
}
}
// 死循环
for (;;) {
try {
// 如果调用await方法,死等
if (!timed)
trip.await();
// 如果调用await(time,unit),基于设置的nans时长决定await的时长
else if (nanos > 0L)
nanos = trip.awaitNanos(nanos);
} catch (InterruptedException ie) {
// 到这,说明线程被中断了
// 查看generation有没有被重置。
// 并且当前broken为false,需要做线程中断后的操作。
if (g == generation && ! g.broken) {
breakBarrier();
throw ie;
} else {
Thread.currentThread().interrupt();
}
}
// 是否是中断唤醒,是就抛异常。
if (g.broken)
throw new BrokenBarrierException();
// 说明被reset了,返回index的数值。或者任务完毕也会被重置
if (g != generation)
return index;
// 指定了等待的时间内,没有等到所有线程都到位
if (timed && nanos <= 0L) {
// 中断任务
breakBarrier();
// 抛出异常
throw new TimeoutException();
}
}
} finally {
lock.unlock();
}
}