一、概述
-
CyclicBarrier(循环屏障)是Java并发编程中的一种同步辅助工具。它允许一组线程相互等待,直到所有线程都到达一个共同的屏障点,然后继续执行后续操作。CyclicBarrier可以用于解决多线程任务的协调和同步问题。
-
CyclicBarrier 的主要作用是使多个线程在某个点上进行同步,等待所有线程都到达该点后再一起继续执行。
- 它类似于一组线程从启动开始跑步,跑到3000米时停下等待其他线程,全部跑到3000米后裁判统计分数,然后再同时开始启跑。
- 我感觉也有点像三峡大坝的船过闸一样的,假如一些船开到三峡大坝的闸内,但是他们不能过闸,但等一定数量的船后,闸门打开,这些船只开始继续航行。
- 实际应用如获取N个数据碎片,都完成后按逻辑合成一个,然后再进行分析。
-
CyclicBarrier的主要特点如下:
- 可以重复使用:一旦所有线程都到达屏障点,屏障就会打开,线程可以继续执行后续操作,然后CyclicBarrier可以被重置并再次使用。
- 计数器机制:CyclicBarrier内部维护了一个计数器,通过指定计数器的值来确定需要等待的线程数量。有没有觉得他像 CountDownLatch ?
- 等待线程阻塞:当一个线程到达屏障点时,它会调用await()方法,然后进入阻塞状态,直到所有线程都到达屏障点。
- 屏障动作:可以为CyclicBarrier指定一个可选的屏障动作(Runnable),当所有线程都到达屏障点时,最后一个到达的线程将执行该屏障动作。
-
CyclicBarrier 常见用途:
- 并行计算:CyclicBarrier 可以用于将计算任务分解给多个线程并行执行,等待所有线程完成后再进行下一步操作。
- 数据加载:CyclicBarrier 可以用于多个线程加载数据,等待所有数据加载完成后再进行后续处理。
- 任务拆分与合并:CyclicBarrier 可以用于将一个大任务拆分为多个子任务,每个子任务由一个线程负责执行,等待所有子任务完成后再进行结果合并。
-
CyclicBarrier 的主要方法包括:
-
await():使当前线程等待其他线程,直到所有线程都调用了 await() 方法,然后所有线程同时继续执行。
-
reset():重置 CyclicBarrier 的状态(屏障和计数器),可以重新使用。
-
getParties():获取参与同步的线程数量。
-
getNumberWaiting():用于获取当前在屏障处等待的线程数量等
-
-
注意:CountDownLatch 是计数减操作,设置初始值,减到为0放行。而 CyclicBarrier 是计数加操作,加到目标值后执行一个动作后放行。
二、使用方法
-
一般用法:声明 CyclicBarrier 对象 barrier,并指定要等待的任务数和回调函数。然后在其他任务中调用 barrier.await ,当有指定数量(下面是3个)任务调用了 barrier.await ,则执行 CyclicBarrier 的回调函数。
import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; public class CyclicBarrierExample { private static final int THREAD_COUNT = 3; public static void main(String[] args) { CyclicBarrier barrier = new CyclicBarrier(THREAD_COUNT, () -> { // 执行屏障动作(最后一个到达的线程执行该动作)。 // 特别说明:能执行到这里说明,规定数量的线程(THREAD_COUNT)都执行了 barrier.await(); }); for (int i = 0; i < THREAD_COUNT; i++) { Thread thread = new Thread(() -> { try { // 执行业务逻辑 barrier.await(); // 线程到达屏障点,等待其他线程 // 所以线程都到达屏障点,并且已经执行屏障作用后,才会执行这里的代码 } catch (InterruptedException | BrokenBarrierException e) { e.printStackTrace(); } }); thread.start(); } } }
三、测试示例1
-
在下面示例中,创建了一个CyclicBarrier对象,并将计数器的值设置为3,表示需要等待3个线程到达屏障点。然后,我们创建了3个线程,并在每个线程中执行一段业务逻辑后调用await()方法,即表示线程到达屏障点并等待其他线程。当所有线程都到达屏障点时,最后一个到达的线程将执行屏障动作(在示例中为打印一条消息)。执行屏障动作后再继续执行线程任务。
package top.yiqifu.study.p004_thread; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; public class Test071_CyclicBarrier { private static final int THREAD_COUNT = 3; public static void main(String[] args) { CyclicBarrier barrier = new CyclicBarrier(THREAD_COUNT, () -> { // 特别说明:能执行到这里说明,规定数量的线程(THREAD_COUNT)都执行了 barrier.await(); String threadName = Thread.currentThread().getName(); System.out.println(threadName+"执行屏障动作(最后一个到达的线程执行该动作)。"); }); for (int i = 0; i < THREAD_COUNT; i++) { Thread thread = new Thread(() -> { try { String threadName = Thread.currentThread().getName(); System.out.println(threadName+ "执行业务逻辑"); System.out.println(threadName+ "到达屏障点,等待其他线程");//注意这里仅示意,由于这句和下面的 barrier.await(); 并不是原子操作,所以有可能输出显示的不是最后 barrier.await(); // 所以线程都到达屏障点,并且已经执行屏障作用后,才会执行这里的代码 System.out.println(threadName+ "完成业务逻辑"); } catch (InterruptedException | BrokenBarrierException e) { e.printStackTrace(); } }); thread.setName("线程"+(i+1)+" "); thread.start(); } } }
四、测试示例2
-
下面示例中,假设三个玩家打游戏BOSS,攻击3次为一个阶段。每个玩家攻击三次,若没有击杀BOSS,则BOSS回血50%,然后玩家继续挑战BOSS的示例代码。
package top.yiqifu.study.p004_thread; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; public class Test071_CyclicBarrierGame { private static final int THREAD_COUNT = 3; public static void main(String[] args) { CyclicBarrier barrier = new CyclicBarrier(THREAD_COUNT, () -> { // 三次没有杀死,Boss 回血 50% GameBoss.Instance.enhancement(); }); for (int i = 0; i < THREAD_COUNT; i++) { Thread thread = new Thread(() -> { try { String threadName = Thread.currentThread().getName(); double attckVal = 0; for (int j = 0; j < 3; j++) { attckVal = Math.random() * 100 % 15; if(GameBoss.Instance.attack(threadName, attckVal, j+1)){ return; } Thread.sleep((int)(Math.random() * 1000 % 1000)); // 模拟玩家操作延时 } barrier.await(); for (int m = 0; ; m++) { attckVal = Math.random() * 100 % 15; if(GameBoss.Instance.attack(threadName, attckVal, m+3)){ return; } Thread.sleep((int)(Math.random() * 1000 % 1000)); // 模拟玩家操作延时 } } catch (InterruptedException | BrokenBarrierException e) { e.printStackTrace(); } }); thread.setName("玩家"+(i+1)+" "); thread.start(); } } public static class GameBoss { public static GameBoss Instance = new GameBoss(); private GameBoss(){ } private double bossValue = 100; public void enhancement(){ bossValue = Math.max(bossValue * 1.5, 100); System.out.println("Boss 回血 50%"); } public synchronized boolean attack(String player, double val, int times){ if(bossValue < 0){ return true; } bossValue -= val; System.out.println(player+ "第"+times+"次发动攻击,伤害="+val); if(bossValue < 0){ System.out.println(player+ "击杀"); return true; } return false; } } }
示例中, GameBoss 类的 attack 使用到了 synchronized 关键字,有关 synchronized 关键字请看 synchronized 使用说明