CyclicBarrier
CyclicBarrier概念
CyclicBarrier有一个屏障的概念,线程到达屏障后,就会挂起。当多个线程到达屏障,这个屏障就会打开,让这些线程继续执行。而且CyclicBarrier可以重复使用。
CyclicBarrier是基于ReentrantLock来实现并发安全的。
CyclicBarrier用法
背景栏:
假设Tom、Jack、Rose三个人去旅游,导游等三个人到齐后分发护照,然后再出发。
板书栏:
public class Test {
public static void main(String[] args) {
CyclicBarrier barrier = new CyclicBarrier(3, () -> {
System.out.println("人到齐了,分发护照");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("一起出发");
});
new Thread(() -> {
System.out.println("Tom来了");
try {
barrier.await();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("Tom拿到护照");
}).start();
new Thread(() -> {
System.out.println("Jack来了");
try {
barrier.await();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("Jack拿到护照");
}).start();
new Thread(() -> {
System.out.println("Rose来了");
try {
barrier.await();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("Rose拿到护照");
}).start();
}
}
要点栏:
要点1:
在构建CyclicBarrier可以指定一个任务,这个任务会在barrier归0时执行,然后再去唤醒挂起的线程,并行执行后续的任务。
要点2:
互相等待的线程,可以指定等待时间,并且在等待过程中,如果有一个线程中断,其他线程也会被唤醒,并且CyclicBarrier无法再继续使用。除非调用reset方法重置CyclicBarrier。
要点3:
如果CyclicBarrier的屏障数值到达0后,会默认重置屏障数值,CyclicBarrier在没有线程中断的情况下是可以重复使用的。
CyclicBarrier源码
基本属性
板书栏:
public class CyclicBarrier {
// 用来记录是否有线程中断
private static class Generation {
boolean broken = false;
}
// 互斥锁
private final ReentrantLock lock = new ReentrantLock();
private final Condition trip = lock.newCondition();
// 用来记录屏障数量,不会对其进行加减操作
private final int parties;
// CyclicBarrier的有参构造传入的任务
private final Runnable barrierCommand;
// 初始化generation
private Generation generation = new Generation();
// 用来记录屏障数量,会对其进行加减操作
private int count;
}
基本方法
板书栏:
public int await() throws InterruptedException, BrokenBarrierException {
try {
// 有参和无参的await方法,都是调用的dowait方法
return dowait(false, 0L);
} catch (TimeoutException toe) {
throw new Error(toe);
}
}
private int dowait(boolean timed, long nanos)
throws
// 当前线程中断抛的异常
InterruptedException,
// 其他线程中断抛的异常
BrokenBarrierException,
// 等待超时抛的异常
TimeoutException {
final ReentrantLock lock = this.lock;
// 加锁
lock.lock();
try {
// 获取generation
final Generation g = generation;
// 判断其他线程是否中断
if (g.broken)
// 其他线程中断,抛出异常
throw new BrokenBarrierException();
// 判断当前线程是否中断
if (Thread.interrupted()) {
// generation中的broken设置为true
// 重置count
// 唤醒其他线程
breakBarrier();
// 当前线程中断抛出异常
throw new InterruptedException();
}
// count减1赋值给index
int index = --count;
// 判断count减1后是否为0
if (index == 0) {
// 说明当前线程是最后一个到达屏障的
boolean ranAction = false;
try {
// 拿到barrier任务
final Runnable command = barrierCommand;
if (command != null)
// barrier任务执行
command.run();
// barrier任务正常执行完成
// ranAction设置为true
ranAction = true;
// 重置generation
// 重置count
// 唤醒其他线程
nextGeneration();
返回屏障数
return 0;
} finally {
// 判断barrier是否正常执行完成
if (!ranAction)
// 进入这里说明barrier任务没有正常执行完成,可能抛了异常
// generation中的broken设置为true
// 重置count
// 唤醒其他线程
breakBarrier();
}
}
// 到这里说明当前线程不是最后一个到达屏障的
for (;;) {
try {
// 判断是否有等待时间
if (!timed)
trip.await();
else if (nanos > 0L)
nanos = trip.awaitNanos(nanos);
} catch (InterruptedException ie) {
// 先判断generation是否被重置(可能有线程执行了reset方法导致被重置)
// 如果没有被重置,判断其他线程是否有中断
if (g == generation && ! g.broken) {
// generation没有被重置,并且其他线程没有中断
// 说明当前线程中断
// generation中的broken设置为true
// 重置count
// 唤醒其他线程
breakBarrier();
// 抛出当前线程中断异常
throw ie;
} else {
// 到这里说明有线程调用了reset方法
// 当前线程设置中断
Thread.currentThread().interrupt();
}
}
// 判断其他线程是否有中断
if (g.broken)
// 抛出其他线程中断异常
throw new BrokenBarrierException();
// 判断generation是否被重置
if (g != generation)
// 到这里有两种情况
// 第一种,有线程调用的reset方法
// 第二种,最后一个线程到达屏障,唤醒了其他线程
return index;
// 判断等待时间是否已过
if (timed && nanos <= 0L) {
breakBarrier();
throw new TimeoutException();
}
}
} finally {
lock.unlock();
}
}
private void breakBarrier() {
generation.broken = true;
count = parties;
trip.signalAll();
}
private void nextGeneration() {
// signal completion of last generation
trip.signalAll();
// set up next generation
count = parties;
generation = new Generation();
}
要点栏:
要点1:
当所有线程到达屏障,重置generation;而当有线程中断,会将generation中的broken设置为true。根据这个来判断await方法是正常返回,还是要抛出异常。
要点2:
如果是当前线程中断,抛出的是InterruptedException异常;如果是其他线程中断,抛出的是BrokenBarrierException异常;如果是等待超时,抛出的是TimeoutException异常。
要点3:
如果在所有线程还未到达屏障时,有线程调用了reset方法,会重置generation和count,并且唤醒已经挂起的线程。被唤醒的线程会正常退出await方法,继续执行任务。