CyclicBarrier介绍

一、概述

CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。

这个屏障之所以用循环修饰,是因为在所有的线程释放彼此之后,这个屏障是可以重新使用的(reset()方法重置屏障点),这一点与CountDownLatch不同。

特点

  • CyclicBarrier是一种同步机制允许一组线程相互等待,等到所有线程都到达一个屏障点才退出await方法,它没有直接实现AQS而是借助ReentrantLock来实现的同步机制。
  • CyclicBarrier是可循环使用的,而CountDownLatch是一次性的,另外它体现的语义也跟CountDownLatch不同
    • CountDownLatch减少计数到达条件采用的是release方式;
    • CyclicBarrier走向屏障点(await)采用的是Acquire方式,Acquire是会阻塞的。
  • CyclicBarrier的另外一个特点,只要有一个线程中断那么屏障点就被打破,所有线程都将被唤醒(CyclicBarrier自己负责这部分实现,不是由AQS调度的),这样也避免了因为一个线程中断引起永远不能到达屏障点而导致其他线程一直等待。
  • 障点被打破的CyclicBarrier将不可再使用(会抛出BrokenBarrierException)除非执行reset操作。

二、CyclicBarrier的方法说明

2.1 构造方法

  • **CyclicBarrier(int parties)**:创建一个新的 CyclicBarrier ,当给定数量的线程(线程)等待它时,它将跳闸,并且当屏障跳闸时不执行预定义的动作。

    1
    2
    3
    4
    public CyclicBarrier(int parties) {
    // 调用含有两个参数的构造函数
    this(parties, null);
    }
  • **CyclicBarrier(int parties, Runnable barrierAction)**:创建一个新的 CyclicBarrier ,当给定数量的线程(线程)等待时,它将跳闸,当屏障跳闸时执行给定的屏障动作,由最后一个进入屏障的线程执行。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public CyclicBarrier(int parties, Runnable barrierAction) {
    // 参与的线程数量小于等于0,抛出异常
    if (parties <= 0) throw new IllegalArgumentException();
    // 设置parties
    this.parties = parties;
    // 设置count
    this.count = parties;
    // 设置barrierCommand
    this.barrierCommand = barrierAction;
    }

2.2 方法

方法 说明
int await() 等待所有 parties 已经在这个障碍上调用了 await。
int await(long timeout, TimeUnit unit) 等待所有 parties 已经在此屏障上调用 await ,或指定的等待时间过去。
int getNumberWaiting() 返回目前在屏障处等待的线程个数。
int getParties() 返回履行这个障碍所需的 parties 数量。
boolean isBroken() 查询这个障碍是否处于破碎状态。
void reset() 将屏障重置为初始状态。

三、使用示例

场景:十名运动员各自准备比赛,需要等待所有运动员都准备好以后,裁判才能说开始然后所有运动员一起开跑

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

/**
* Created by xiaowei
* Date 2022/12/3
* Description CyclicBarrier使用
*/
public class CyclicBarrierDemo {
public static void main(String[] args) {
final CyclicBarrier cyclicBarrier = new CyclicBarrier(10,()->{
System.out.println("所有人都准备好了裁判开始了");
});
for (int i = 0; i < 10; i++) {
//lambda中只能只用final的变量
final int times = i;
new Thread(() -> {
try {
System.out.println("子线程" + Thread.currentThread().getName() + "正在准备");

Thread.sleep(1000 * times);

System.out.println("子线程" + Thread.currentThread().getName() + "准备好了");

// 调用该方法时,当前线程将会被阻塞,需要等待其它线程都到达了才能继续
cyclicBarrier.await();

System.out.println("子线程" + Thread.currentThread().getName() + "开始跑了");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}

运行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
子线程Thread-0正在准备
子线程Thread-0准备好了
子线程Thread-1正在准备
子线程Thread-2正在准备
子线程Thread-3正在准备
子线程Thread-4正在准备
子线程Thread-5正在准备
子线程Thread-6正在准备
子线程Thread-7正在准备
子线程Thread-8正在准备
子线程Thread-9正在准备
子线程Thread-1准备好了
子线程Thread-2准备好了
子线程Thread-3准备好了
子线程Thread-4准备好了
子线程Thread-5准备好了
子线程Thread-6准备好了
子线程Thread-7准备好了
子线程Thread-8准备好了
子线程Thread-9准备好了
所有人都准备好了裁判开始了
子线程Thread-9开始跑了
子线程Thread-0开始跑了
子线程Thread-2开始跑了
子线程Thread-1开始跑了
子线程Thread-3开始跑了
子线程Thread-5开始跑了
子线程Thread-8开始跑了
子线程Thread-4开始跑了
子线程Thread-7开始跑了
子线程Thread-6开始跑了

从结果可以看出,当 cyclicBarrier.await();增加到10以后,自动放行,所有运动员同时开跑。

总结

  • CountDownLatch 是线程组之间的等待,即一个(或多个)线程等待N个线程完成某件事情之后再执行;而 CyclicBarrier 则是线程组内的等待,即每个线程相互等待,即N个线程都被拦截之后,然后依次执行。
  • CountDownLatch 是减计数方式,而 CyclicBarrier 是加计数方式。
  • CountDownLatch 计数为0无法重置,而 CyclicBarrier 计数达到初始值,则可以重置。
  • CountDownLatch 不可以复用,而 CyclicBarrier 可以复用。

CyclicBarrier介绍
http://example.com/2023/03/31/CyclicBarrier介绍/
作者
程序员小魏
发布于
2023年3月31日
许可协议