Java并发编程之CyclicBarrier
简介
CyclicBarrier字面意思是循环屏障,它可以实现线程间的计数等待。当线程到达屏障点时会依次进入等待状态,直到最后一个线程进入屏障点时会唤醒等待的线程继续运行。
CyclicBarrier和CountDownLatch类似,区别在于CountDownLatch只能使用一次,当计数器归零后,CountDownLatch的await等方法都会直接返回。而CyclicBarrier是可以重复使用的,当计数器归零后,计数器和CyclicBarrier状态都会被重置。
CyclicBarrier的使用
构造方法介绍
CyclicBarrier(int parties):创建CyclicBarrier,指定计数器值(等待线程数量)。
CyclicBarrier(int parties, Runnable barrierAction):创建CyclicBarrier,指定计数器值(等待线程数量)和计数器归零后(最后一个线程到达)要执行的任务。
核心方法介绍
await():阻塞当前线程,直到计数器归零被唤醒或者线程被中断。
await(long timeout, TimeUnit unit):阻塞当前线程,直到计数器归零被唤醒、线程被中断或者超时返回。
CyclicBarrier例子
等待所有玩家准备就绪,游戏才开始,每一轮游戏的开始意味着CyclicBarrier已经重置,可以开始新一轮的计数。
public class Demo {
public static void main(String[] args) {
//创建CyclicBarrier并指定计数器值为5,以及计数器为0后要执行的任务
CyclicBarrier cyclicBarrier = new CyclicBarrier(5, () -> {
System.out.println("---游戏开始---");
System.out.println("---五票赞成,游戏结束---");
});
Runnable runnable = () -> {
//重复使用CyclicBarrier5次
for(int i = 0; i < 5; i++){
System.out.println(Thread.currentThread().getName() + ":准备就绪");
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
};
Thread thread1 = new Thread(runnable, "一号玩家");
Thread thread2 = new Thread(runnable, "二号玩家");
Thread thread3 = new Thread(runnable, "三号玩家");
Thread thread4 = new Thread(runnable, "四号玩家");
Thread thread5 = new Thread(runnable, "五号玩家");
thread1.start();
thread2.start();
thread3.start();
thread4.start();
thread5.start();
}
}
/*
* 循环输出5次
* 输出结果:
* 一号玩家:准备就绪
* 三号玩家:准备就绪
* 二号玩家:准备就绪
* 五号玩家:准备就绪
* 四号玩家:准备就绪
* ---游戏开始---
* ---五票赞成,游戏结束---
* 三号玩家:准备就绪
* 一号玩家:准备就绪
* 五号玩家:准备就绪
* ......
*/
破损的CyclicBarrier
在使用CyclicBarrier中,假设总的等待线程数量为5,现在其中一个线程被中断了,被中断的线程将抛出InterruptedException异常,而其他4个线程将抛出BrokenBarrierException异常。
BrokenBarrierException异常表示当前的CyclicBarrier已经破损,可能不能等待所有线程到齐了,避免其他线程永久的等待。
CyclicBarrier的源码
CyclicBarrier是基于显式锁ReentrantLock来实现的,CyclicBarrier很多方法都使用显式锁做了同步处理,await方法的等待唤醒也是通过Condition实现的。
CyclicBarrier的成员变量:
//显式锁
private final ReentrantLock lock = new ReentrantLock();
//用于显式锁的Condition
private final Condition trip = lock.newCondition();
//线程数量
private final int parties;
//当所有线程到达屏障点后执行的任务
private final Runnable barrierCommand;
//Generation内部有一个broken变量,用于标识CyclicBarrier是否破损
private Generation generation = new Generation();
//用于递减的线程数量,在每一轮结束后会被重置为parties
private int count;
await方法里是调用的dowait方法,dowait方法源码:
private int dowait(boolean timed, long nanos)throws InterruptedException, BrokenBarrierException,TimeoutException {
final ReentrantLock lock = this.lock;
lock.lock();
try {
final Generation g = generation;
//如果CyclicBarrier已破损,则抛出BrokenBarrierException异常
if (g.broken)
throw new BrokenBarrierException();
//如果当前线程已经中断,则将CyclicBarrier标记为已破损并抛出InterruptedException异常
if (Thread.interrupted()) {
breakBarrier();
throw new InterruptedException();
}
int index = --count;
//index == 0表示所有线程都到达了屏障点
if (index == 0) { // tripped
boolean ranAction = false;
try {
//执行线程到齐后需要执行的任务
final Runnable command = barrierCommand;
if (command != null)
command.run();
ranAction = true;
//唤醒所有等待的线程并重置CyclicBarrier
nextGeneration();
return 0;
} finally {
if (!ranAction)
breakBarrier();
}
}
//线程没到齐,阻塞当前线程
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 {
// We're about to finish waiting even if we had not
// been interrupted, so this interrupt is deemed to
// "belong" to subsequent execution.
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();
}
}
nextGeneration方法:
private void nextGeneration() {
//唤醒所有等待的线程
trip.signalAll();
//重置CyclicBarrier
count = parties;
generation = new Generation();
}