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

posted @   欢乐豆123  阅读(42)  评论(0编辑  收藏  举报
努力加载评论中...
点击右上角即可分享
微信分享提示