CyclicBarrier的介绍
CyclicBarrier的介绍
概要
CyclicBarrier(循环栅栏/循环屏障)是JUC并发包中的一个同步工具类,它允许一组线程在执行过程中互相等待,直到所有线程都达到某个公共屏障点(barrier point),然后这些线程再一起继续执行。并且 CyclicBarrier 功能可重复使用。它适用于那些多个线程需要协调同步的场景。
如下图:
一、如何理解CyclicBarrier
举个例子,比如小明,小美,小华,小丽几人终于历经多年课本出题历程,高考结束,相约一起聚餐,然而他们每个人到达约会地点的耗时都一样,有的人会早到,有的人会晚到,但是他们要都到了以后才可以决定点那些菜。
这个时候我们就可以使用JUC包中为我们提供了一个同步工具类来模拟这类场景,CyclicBarrier,利用CyclicBarrier类可以实现一组线程相互等待,当所有线程都到达某个屏障点后再进行后续的操作。这里每个人相当于一个线程,而餐厅就是 CyclicBarrier。
介绍:CyclicBarrier可以使一定数量的线程反复地在栅栏位置处汇集。当线程到达栅栏位置时将调用await方法,这个方法将阻塞直到所有线程都到达栅栏位置。如果所有线程都到达栅栏位置,那么栅栏将打开,此时所有的线程都将被释放,而栅栏将被重置以便下次使用。
CyclicBarrier字面意思是"可重复使用的栅栏",CyclicBarrier 和 CountDownLatch 很像,只是 CyclicBarrier 可以有不止一个栅栏,因为它的栅栏(Barrier)可以重复使用(Cyclic)。
二、CyclicBarrier核心方法
1. 成员变量
1 public class CyclicBarrier { 2 private static class Generation { 3 // 用于标记当前屏障是否损坏 4 boolean broken = false; 5 } 6 7 // 提供线程安全的机制,确保多个线程在访问和修改共享数据时不会出现竞态条件 8 private final ReentrantLock lock = new ReentrantLock(); 9 10 // trip 是一个通过 ReentrantLock 创建的条件变量 (Condition),它的作用是管理线程在屏障处的等待与唤醒 11 private final Condition trip = lock.newCondition(); 12 // 表示需要参与屏障的线程数量 13 private final int parties; 14 // 表示屏障动作,在最后一个线程到达屏障时执行 15 private final Runnable barrierCommand; 16 // 创建一个新的屏障代 17 private Generation generation = new Generation(); 18 19 //记录当前还未到达屏障的线程数量的变量 20 private int count; 21 22 //... 23 }
2. 构造函数
构造函数的部分源码如下:
1 /** 2 * 构造方法 1:指定屏障等待的线程数,默认不执行任何额外操作(即不传递 barrierAction)。 3 * 4 * @param parties 需要等待的线程数 5 */ 6 public CyclicBarrier(int parties) { 7 // 调用第二个构造方法,传递 parties 参数和 null(表示不执行额外操作) 8 this(parties, null); 9 } 10 11 /** 12 * 构造方法 2:指定屏障等待的线程数,并可指定在所有线程到达屏障点时执行的额外操作。 13 * 14 * @param parties 屏障需要等待的线程数 15 * @param barrierAction 所有线程到达屏障时执行的额外操作,如果为 null 则没有额外操作。方便处理更复杂的业务场景。 16 * @throws IllegalArgumentException 如果 parties 小于等于 0,抛出非法参数异常 17 */ 18 public CyclicBarrier(int parties, Runnable barrierAction) { 19 if (parties <= 0) throw new IllegalArgumentException(); 20 this.parties = parties; 21 //初始化当前等待的线程数为parties 22 this.count = parties; 23 this.barrierCommand = barrierAction; 24 }
3. await方法
1 //非定时等待 2 public int await() throws InterruptedException, BrokenBarrierException { 3 try { 4 return dowait(false, 0L); 5 } catch (TimeoutException toe) { 6 throw new Error(toe); 7 } 8 } 9 10 //定时等待 11 public int await(long timeout, TimeUnit unit) throws InterruptedException, BrokenBarrierException, TimeoutException { 12 return dowait(true, unit.toNanos(timeout)); 13 }
await() 表示自己已经到达栅栏。其中BrokenBarrierException 表示栅栏已经被破坏破坏的原因可能是其中一个线程 await() 时被中断或者超时。可以看到不管是定时等待还是非定时等待,它们都调用了dowait() 方法。
4. dowait方法
dowait 方法是 CyclicBarrier 实现的核心部分,它确保每个线程在屏障处等待直到所有线程到达,同时处理中断、超时和屏障破坏的情况。最后一个到达屏障的线程将执行指定的操作(barrierCommand)并重置屏障进入下一代。
部分源码如下:
1 /** 2 * 3 * @param timed 该参数控制是否进行有超时的等待。true-表示方法将会等待指定的最大时间(由 nanos 决定)。false-表示方法将会一直等待,直到所有线程到达屏障。 4 * @param nanos 若timed为true,该参数表示最大等待的时间,单位是纳秒。若该时间内其他线程没有到达屏障,当前线程将抛出TimeoutException异常。timed为false 该参数不会起作用。 5 **/ 6 private int dowait(boolean timed, long nanos) 7 throws InterruptedException, BrokenBarrierException, TimeoutException { 8 // 获取锁对象,确保线程安全 9 final ReentrantLock lock = this.lock; 10 lock.lock(); 11 try { 12 // 获取当前的屏障代(Generation),用于检查屏障状态 13 final Generation g = generation; 14 15 // 1. 如果屏障被破坏 16 if (g.broken) 17 throw new BrokenBarrierException(); 18 19 // 2. 如果当前线程被中断 20 if (Thread.interrupted()) { 21 breakBarrier(); 22 throw new InterruptedException(); 23 } 24 25 // 减少计数器,表示当前线程到达屏障 26 int index = --count; 27 28 // 如果这是最后一个到达屏障的线程 29 if (index == 0) { // 所有线程到达屏障 30 boolean ranAction = false; 31 try { 32 // 获取屏障的任务(barrierCommand),如果存在,则执行 33 final Runnable command = barrierCommand; 34 if (command != null) 35 command.run(); 36 ranAction = true; 37 38 // 开启下一代屏障,唤醒所有线程 39 nextGeneration(); 40 return 0; // 返回索引 0,表示此线程是最后一个到达屏障的 41 } finally { 42 // 如果任务执行失败,破坏屏障 43 if (!ranAction) 44 breakBarrier(); 45 } 46 } 47 48 // 循环等待,直到屏障被触发、破坏、中断或超时 49 for (;;) { 50 try { 51 if (!timed) { 52 // 如果没有超时限制,调用 trip.await() 等待 53 trip.await(); 54 } else if (nanos > 0L) { 55 // 如果设置了超时时间,则调用 trip.awaitNanos() 等待 56 nanos = trip.awaitNanos(nanos); 57 } 58 } catch (InterruptedException ie) { 59 // 如果线程中断,且屏障未被破坏,破坏屏障并抛出中断异常 60 if (g == generation && !g.broken) { 61 breakBarrier(); 62 throw ie; 63 } else { 64 // 如果屏障已被触发,将中断标志重新设置 65 Thread.currentThread().interrupt(); 66 } 67 } 68 69 // 如果屏障已被破坏,抛出 BrokenBarrierException 70 if (g.broken) 71 throw new BrokenBarrierException(); 72 73 // 如果屏障已被触发,返回线程的索引 74 if (g != generation) 75 return index; 76 77 // 如果超时且时间已耗尽,破坏屏障并抛出 TimeoutException 78 if (timed && nanos <= 0L) { 79 breakBarrier(); 80 throw new TimeoutException(); 81 } 82 } 83 } finally { 84 // 无论如何,确保释放锁 85 lock.unlock(); 86 } 87 }
5. nextGeneration方法
每次屏障的参与线程数达到指定数量(即parties的值)后,会调用该方法重置屏障的状态,以便后续可以继续复用。
/** * 启动下一代屏障 */ private void nextGeneration() { trip.signalAll(); // 唤醒所有等待线程 count = parties; // 重置计数器 generation = new Generation(); // 创建新屏障 }
6. breakBarrier 方法
当屏障被损坏时(例如,线程中断或超时等异常情况),所有等待线程会立即被唤醒。
/** * 损坏屏障 */ private void breakBarrier() { generation.broken = true; // 标记屏障已损坏 count = parties; // 重置计数器 trip.signalAll(); // 唤醒所有等待线程 }
破坏屏障的主要目的是在某些特殊情况下,停止所有线程的正常等待并避免它们继续等待。
三、使用场景
1. 应用示例1
上面朋友聚餐的案例,可以用下面代码来简单实现:
1 public class CyclicBarrierTest { 2 3 /** 4 * 5 * @param args 6 */ 7 public static void main(String[] args) { 8 ExecutorService service = Executors.newCachedThreadPool(); 9 CyclicBarrier barrier = new CyclicBarrier(5, new Runnable() { 10 @Override 11 public void run() { 12 System.out.println("全部到达" + Thread.currentThread().getName() + "呼叫服务员开始点餐!"); 13 service.shutdown(); 14 15 } 16 }); 17 for (int j = 0; j < 5; j++) { 18 service.execute(new Runnable() { 19 @Override 20 public void run() { 21 try { 22 Thread.sleep(1000); 23 System.out.println(Thread.currentThread().getName() + "同学到达"); 24 barrier.await(); 25 System.out.println(Thread.currentThread().getName() + "同学点餐"); 26 } catch (InterruptedException e) { 27 e.printStackTrace(); 28 } catch (BrokenBarrierException e) { 29 e.printStackTrace(); 30 } 31 } 32 33 }); 34 } 35 service.shutdown(); 36 } 37 } 38 39 //执行结果 40 pool-1-thread-1同学到达 41 pool-1-thread-2同学到达 42 pool-1-thread-4同学到达 43 pool-1-thread-3同学到达 44 pool-1-thread-5同学到达 45 全部到达pool-1-thread-5呼叫服务员开始点餐! 46 pool-1-thread-5同学点餐 47 pool-1-thread-2同学点餐 48 pool-1-thread-1同学点餐 49 pool-1-thread-3同学点餐 50 pool-1-thread-4同学点餐
2. 应用示例2
下面的例子说明循环屏障的重复使用:
1 public class CyclicBarrierTest2 { 2 public static void main(String[] args) { 3 // 定义一个 CyclicBarrier,每次 3 个线程全部到达屏障时,执行一个任务 4 CyclicBarrier barrier = new CyclicBarrier(3, () -> { 5 System.out.println("所有线程完成阶段,开始下一阶段!"); 6 }); 7 8 // 创建并启动 3 个线程 9 for (int i = 0; i < 3; i++) { 10 new Thread(new Worker(barrier), "线程-" + (i + 1)).start(); 11 } 12 } 13 } 14 15 class Worker implements Runnable { 16 private final CyclicBarrier barrier; 17 18 public Worker(CyclicBarrier barrier) { 19 this.barrier = barrier; 20 } 21 22 @Override 23 public void run() { 24 try { 25 for (int phase = 1; phase <= 2; phase++) { 26 System.out.println(Thread.currentThread().getName() + " 正在执行阶段 " + phase); 27 // 模拟工作耗时 28 Thread.sleep((long) (Math.random() * 1000)); 29 30 // 等待其他线程完成该阶段 31 System.out.println(Thread.currentThread().getName() + " 完成阶段 " + phase + ",等待其他线程"); 32 barrier.await(); 33 } 34 } catch (InterruptedException | BrokenBarrierException e) { 35 e.printStackTrace(); 36 } 37 } 38 } 39 40 // 执行结果 41 // 线程-1 正在执行阶段 1 42 // 线程-3 正在执行阶段 1 43 // 线程-2 正在执行阶段 1 44 // 线程-2 完成阶段 1,等待其他线程 45 // 线程-3 完成阶段 1,等待其他线程 46 // 线程-1 完成阶段 1,等待其他线程 47 // 所有线程完成阶段,开始下一阶段! 48 // 线程-1 正在执行阶段 2 49 // 线程-2 正在执行阶段 2 50 // 线程-3 正在执行阶段 2 51 // 线程-2 完成阶段 2,等待其他线程 52 // 线程-3 完成阶段 2,等待其他线程 53 // 线程-1 完成阶段 2,等待其他线程 54 // 所有线程完成阶段,开始下一阶段!
CyclicBarrier barrier = new CyclicBarrier(3, () -> {...}); 其中,3 表示屏障需要等待 3个线程完成,触发一个任务。
每个线程执行 2 个阶段任务,每个阶段中线程完成后调用 barrier.await() 等待其他线程。当所有线程到达屏障时,所有线程会继续执行下一阶段任务。
四、CyclicBarrier和CountDownLatch的区别
1. 计数器使用的次数
CountDownLatch的计数器只能使用一次,而CyclicBarrier的计数器可以使用reset()方法重置,可以使用多次,所以CyclicBarrier能够处理更为复杂的场景;
2. 用途
CountDownLatch允许一个或多个线程等待一组事件的产生,而CyclicBarrier用于等待其他线程运行到栅栏位置。
3. 额外功能
CyclicBarrier还提供了一些其他有用的方法,比如getNumberWaiting()方法可以获得CyclicBarrier阻塞的线程数量,isBroken()方法用来了解阻塞的线程是否被中断;
五、CyclicBarrier总结
1. 屏障点的设计
CyclicBarrier 用于多个线程同步,确保所有线程都到达一个屏障点时才能继续执行。其核心功能是:
- 每个线程调用 await() 方法时会进入阻塞状态,直到所有线程到达屏障点。
- 当所有线程到达屏障点时,屏障会被“触发”,即允许所有线程继续执行。
2. 计数器(count)
CyclicBarrier 通过一个计数器 count 来跟踪有多少个线程已经到达屏障。count 的初始值是参与屏障的线程数(parties),每当一个线程到达屏障时,count 会递减。直到 count 减到 0,表示所有线程都已到达屏障点。
3. ReentrantLock 和 Condition
- ReentrantLock:用于实现线程之间的互斥,确保对共享资源(如 count)的访问是线程安全的。
- Condition:用于线程之间的协调,CyclicBarrier 使用 Condition来让线程在屏障点等待,并在所有线程到达屏障时唤醒它们。
CyclicBarrier 通过 Condition 和 ReentrantLock 的配合使用,来实现高效的线程同步管理。其核心是利用 trip.await() 挂起线程、trip.signalAll() 唤醒线程,结合屏障状态 Generation,实现可重用的屏障同步机制。
参考链接:
https://juejin.cn/post/6977549754217529358