前言
商阳剑:商阳剑巧妙灵活,CyclicBarrier 可以灵活地设置线程同步的条件和重复使用,如同商阳剑般变化多端。
CyclicBarrier直译过来就是“循环栅栏”,在Java并发编程中,用于控制一组线程,先到达栅栏的等待其他未到达的线程,都到达后再一起继续执行,跑到下一个栅栏,也是同样等待所有线程都到达栅栏再继续到下一个栅栏,以此往复。
CyclicBarrier内有一个计数器,在构造函数初始化时会初始计数器的值,当调用await方法时,计数器会减一并将当前线程进行阻塞,表示次线程已到达栅栏,等待其他线程全部执行完毕,也就是计数器的值为0时。
CyclicBarrier方法详解
- CyclicBarrier(int parties):构造方法,其中parties表示计数器的数量(一般parties为线程数)
- CyclicBarrier(int parties, Runnable barrierAction):构造方法,barrierAction表示当线程到达屏障后先执行一个预定义的动作,再进行阻塞等待。
- int await():计数器减一,阻塞等待其他线程到达栅栏。返回的值表示当前线程在本次屏障等待中到达的序号,序号从0开始。
- int await(long timeout, TimeUnit unit):添加了一个超时时间,如果未在给定的timeout时间内被栅栏放行,将会抛出InterruptedException的异常。
- reset():重置计数器,重置后,如果有其他线程在await栅栏放行,在await的线程将会抛出BrokenBarrierException的异常。
- boolean isBroken():检查CyclicBarrier是否处于破损状态。当以下情况发生时CyclicBarrier会进入破损状态:
- 某个等待的线程被中断。
- 某个等待的线程超时。
- 屏障动作(如果有指定)在执行过程中抛出异常。
- int getNumberWaiting():获取正在await的数量。
- int getParties():获取总数,也就是在构造方法中我们指定的parties值。
举个栗子🌰
在我们玩一些PVP游戏中,我们一般在开始游戏的时候都会有 匹配玩家->选择角色->加载进入 的过程,而每一个流程都需要所有玩家全部确认完成后才能继续,每一个过程相当于就是一个栅栏,拦住全部的玩家,直到所有的玩家完成才进行下一步。 比较经典的一张图:
代码实现:
public class CyclicBarrierCase {
//匹配游戏->选择角色->加载进入
public static void produce(){
List<String> player = new ArrayList<>();
player.add("玩家一");
player.add("玩家二");
player.add("玩家三");
player.add("玩家四");
CyclicBarrier barrier = new CyclicBarrier(player.size());
for (int i = 0; i < player.size(); i++) {
new Thread(() -> {
String threadName = Thread.currentThread().getName();
sleep(); //准备游戏时间
System.out.println(threadName+"准备完成");
try {
barrier.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (BrokenBarrierException e) {
throw new RuntimeException(e);
}
sleep(); //选择角色时间
System.out.println(threadName + "选择完成");
try {
barrier.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (BrokenBarrierException e) {
throw new RuntimeException(e);
}
sleep(); //加载进入时间
System.out.println(threadName + "加载完成");
try {
barrier.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (BrokenBarrierException e) {
throw new RuntimeException(e);
}
},player.get(i)+"线程").start();
}
}
public static void sleep(){
//睡1到3秒的随机数
int random = RandomUtil.randomInt(1000, 3000);
try {
Thread.sleep(random);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
produce();
}
}
执行结果:
await时的异常
调用await时,会让我们捕获两个异常,一个是BrokenBarrierException另一个是InterruptedException。
出现BrokenBarrierException这种情况可能是CyclicBarrier的某个线程在等待期间被中断,或者CyclicBarrier被重置。这通常意味着CyclicBarrier无法正常工作,处理的方案可能有捕获异常并记录日志,通过某种共享状态或消息机制通知其他线程CyclicBarrier已损坏,执行一些恢复操作来尽量弥补或减轻由于栅栏损坏导致的影响。
出现InterruptedException是因为在调用await(long timeout, TimeUnit unit)我们指定了超时时间,当指定时间过后,线程还没有被放行,抛出超时异常,处理超时的策略可能包括重试机制或者回退逻辑。但重要的是要确保所有的线程在超时后都能正确地处理这种情况,避免资源泄漏或者线程阻塞。