java循环屏障,Java并发编程系列23|循环屏障CyclicBarrier

本文转载自【微信公众号:java进阶架构师,ID:java_jiagoushi】经微信公众号授权转载,如需转载与原文作者联系

df27f300ad924cd35fd912cbf76b3910.png

315b6f477b77ca54e059d222a56f61d0.png

本篇介绍第二个并发工具类CyclicBarrier,CyclicBarrier的字面意思是可循环使用(Cyclic)的屏障(Barrier),分以下部分介绍:

CyclicBarrier的使用CyclicBarrier与CountDownLatch比较CyclicBarrier源码解析1. CyclicBarrier的使用

CyclicBarrier要做的事情是让一组线程到达一个屏障时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程一起执行。

1.1 API

CyclicBarrier(int parties)

:构造方法,parties表示拦截线程的数量。

CyclicBarrier(int parties, Runnable barrierAction)

:barrierAction用于在线程到达屏障时优先执行b,用于处理更加复杂的业务场景。

await()

:将当前线程阻塞,等到所有的线程都到达指定的临界点后一起执行。

getNumberWaiting()

:获取当前有多少个线程阻塞等待在临界点上。

reset()

:将屏障重置为初始状态。

1.2 使用举例

举个例子说明CyclicBarrier的使用:8个运动员参加比赛,运动员可能到达赛场的时间不一样,要等8个运动员到齐了才开始比赛,代码如下:

public class CyclicBarrierTest {

private static CyclicBarrier barrier = new CyclicBarrier(8, () -> {

System.out.println("所有运动员入场,裁判员一声令下!!!");

});

public static void main(String[] args) {

System.out.println("运动员准备进场,全场欢呼......");

for (int i = 0; i < 8; i++) {

new Thread() {

public void run() {

System.out.println(Thread.currentThread().getName() + " 运动员到达起点,准备好了!!!");

try {

barrier.await();// 运动员等待,等所有运动员全部到齐后一起开始比赛

} catch (Exception e) {

e.printStackTrace();

}

System.out.println(Thread.currentThread().getName() + " 运动员出发!!!");

};

}.start();

}

}

}

输出结果:

运动员准备进场,全场欢呼......

Thread-0 运动员到达起点,准备好了!!!

Thread-2 运动员到达起点,准备好了!!!

Thread-4 运动员到达起点,准备好了!!!

Thread-1 运动员到达起点,准备好了!!!

Thread-3 运动员到达起点,准备好了!!!

Thread-5 运动员到达起点,准备好了!!!

Thread-6 运动员到达起点,准备好了!!!

Thread-7 运动员到达起点,准备好了!!!

所有运动员入场,裁判员一声令下!!!

Thread-7 运动员出发!!!

Thread-0 运动员出发!!!

Thread-1 运动员出发!!!

Thread-4 运动员出发!!!

Thread-2 运动员出发!!!

Thread-6 运动员出发!!!

Thread-5 运动员出发!!!

Thread-3 运动员出发!!!

2. 与CountDownLatch比较

CountDownLatch用于一个线程等待若干个其他线程执行完任务之后才执行,强调一个线程等待,这个线程会阻塞。而CyclicBarrier用于一组线程互相等待至某个状态,然后这一组线程再同时执行,强调的是多个线程互等,这多个线程阻塞,等大家都完成,再携手共进。CountDownLatch是不能复用的,而CyclicLatch是可以复用的。使用reset()方法将屏障重置为初始状态之后就可以复用。CyclicBarrier提供了更多的方法,能够通过getNumberWaiting()获取阻塞线程的数量,通过isBroken()方法可以知道阻塞的线程是否被中断。3. 源码分析

CyclicBarrier是通过Lock的Condition实现的,每个CyclicBarrier对应个Lock锁和该锁的condition条件。

创建CyclicBarrier时设置一个count计数,当调用await()时做两件事:①将count-1 ②将线程阻塞并构造成结点加入condition条件队列。

当count变为0时,达到等待线程数量要求,condition将条件队列中的线程全部唤醒。

3.1 类结构

public class CyclicBarrier {

private static class Generation { // 内部类,当有parties个线程到达barrier就会更新换代

boolean broken = false; // 是否损坏

}

private final ReentrantLock lock = new ReentrantLock(); // 重入锁

private final Condition trip = lock.newCondition();

private final int parties; // 等待线程总数量

private final Runnable barrierCommand; // 达到等待线程数量后执行的线程

private Generation generation = new Generation(); // 当有parties个线程到达barrier,就会更新换代

private int count; // 记录当前线程数量

}

3.2 构造方法

将parties设置为count值,设置达到等待线程数量后优先执行的线程

public CyclicBarrier(int parties, Runnable barrierAction) {

if (parties <= 0) throw new IllegalArgumentException();

this.parties = parties; // 保存parties可循环使用

this.count = parties; // 将parties设置为count值

this.barrierCommand = barrierAction;// 设置达到等待线程数量后优先执行的线程

}

3.3 await()

await()方法:

①将count-1

②将线程阻塞并构造成结点加入condition条件队列。

③当count变为0时,condition将条件队列中的线程全部唤醒。

public int await() throws InterruptedException, BrokenBarrierException {

try {

return dowait(false, 0L);

} catch (TimeoutException toe) {

throw new Error(toe); // cannot happen

}

}

private int dowait(boolean timed, long nanos) throws InterruptedException, BrokenBarrierException, TimeoutException {

final ReentrantLock lock = this.lock;

lock.lock();

try {

final Generation g = generation;

if (g.broken)

throw new BrokenBarrierException();

if (Thread.interrupted()) {

breakBarrier(); // 代失效,唤醒所有线程

throw new InterruptedException();

}

int index = --count; // 计数

if (index == 0) { // 达到要求数量

boolean ranAction = false;

try {

final Runnable command = barrierCommand;

if (command != null)

command.run(); // 达到等待线程数量后执行barrierCommand

ranAction = true;

nextGeneration(); // 唤醒本代所有线程,生成新一代,重置count

return 0;

} finally {

if (!ranAction)

breakBarrier();

}

}

// 线程数量未达到要求数量,将线程挂起等待

for (;;) {

try {

if (!timed)

trip.await(); // 将线程加入condition队列挂起

else if (nanos > 0L)

nanos = trip.awaitNanos(nanos);

} catch (InterruptedException ie) {

if (g == generation && !g.broken) {

breakBarrier();

throw ie;

} else {

Thread.currentThread().interrupt();

}

}

// 特殊情况处理

if (g.broken)

throw new BrokenBarrierException();

if (g != generation)

return index;

if (timed && nanos <= 0L) {

breakBarrier();

throw new TimeoutException();

}

}

} finally {

lock.unlock();

}

}

// 当前代失效,唤醒所有线程

private void breakBarrier() {

generation.broken = true;

count = parties;

trip.signalAll();

}

// 唤醒本代所有线程,生成新一代,重置count

private void nextGeneration() {

trip.signalAll();

count = parties;

generation = new Generation();

}

4. 总结

CyclicBarrier要做的事情是让一组线程到达一个屏障时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。

注意CyclicBarrier与CountDownLatch的区别:CountDownLatch用于一个线程等待若干个其他线程执行完任务之后才执行,而CyclicBarrier强调的是多个线程互等,等大家都完成,再携手共进。此外,CyclicBarrier功能更加强大,可以循环使用。

CyclicBarrier是通过Lock的Condition实现的,每个CyclicBarrier对应个Lock锁和该锁的condition条件。创建CyclicBarrier时设置一个count计数,当调用await()时做两件事:①将count-1 ②将线程阻塞并构造成结点加入condition条件队列。当count变为0时,达到等待线程数量要求,condition将条件队列中的线程全部唤醒。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值