1快速了解


首先看下JDK中的介绍:

/**
 * A synchronization aid that allows a set of threads to all wait for
 * each other to reach a common barrier point.  CyclicBarriers are
 * useful in programs involving a fixed sized party of threads that
 * must occasionally wait for each other. The barrier is called
 * <em>cyclic</em> because it can be re-used after the waiting threads
 * are released.

CyclicBarrier是一个同步工具类,它允许一组线程在到达某个栅栏点(common barrier point)互相等待,发生阻塞,直到最后一个线程到达栅栏点,栅栏才会打开,处于阻塞状态的线程恢复继续执行.它非常适用于一组线程之间必需经常互相等待的情况。CyclicBarrier字面理解是循环的栅栏,之所以称之为循环的是因为在等待线程释放后,该栅栏还可以复用。

纳尼?没看懂?那这样,说个实际场景你必然就知道这东东是做啥的了得意

LOL玩过吧,是不是要十个人全部准备加载100%之后才能进游戏,要是有个人加载特别慢,那剩下的人就得等着~

明白了?嘿嘿,代码实现一下,你再看看,看不懂就去下载LOL吧~  //我亚索贼溜~

public class CyclicBarrierTest {
    public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(5);
        CyclicBarrier barrier = new CyclicBarrier(5);//参数为阻塞线程 
        List<String> nameList = new ArrayList<>();
        Collections.addAll(nameList, "-----盲僧-----", "-----剑圣-----", "-----卡莎-----", "-----风男-----", "-----蠢劫-----","-----大嘴-----","-----蛤蟆-----");
        for (int i = 0; i < 5; i++) {
            service.execute(new Player("玩家" + nameList.get(0), barrier));
            nameList.remove(0);
        }
        service.shutdown();
    }
}
class Player implements Runnable {
    private final String name;
    private final CyclicBarrier barrier;
    public Player(String name, CyclicBarrier barrier) {
        this.name = name;
        this.barrier = barrier;
    }
    public void run() {
        try {
            TimeUnit.SECONDS.sleep(1 + (new Random().nextInt(3)));
            System.out.println(name + "已准备,等待其他玩家准备...");
            barrier.await();
            TimeUnit.SECONDS.sleep(1 + (new Random().nextInt(3)));
            System.out.println(name + "已加入游戏");
        } catch (InterruptedException e) {
            System.out.println(name + "离开游戏");
        } catch (BrokenBarrierException e) {
            System.out.println(name + "离开游戏");
        }
    }
}

这是打印结果:

玩家-----蠢劫-----已准备,等待其他玩家准备...
玩家-----风男-----已准备,等待其他玩家准备...
玩家-----盲僧-----已准备,等待其他玩家准备...
玩家-----卡莎-----已准备,等待其他玩家准备...
玩家-----剑圣-----已准备,等待其他玩家准备...
玩家-----卡莎-----已加入游戏
玩家-----剑圣-----已加入游戏
玩家-----盲僧-----已加入游戏
玩家-----蠢劫-----已加入游戏
玩家-----风男-----已加入游戏

怎么样~

这个类有两个构造方法:

public CyclicBarrier(int parties) public CyclicBarrier(int parties, Runnable barrierAction) 

参数parties指定线程数量,当指定的线程值都到达栅栏点时,栅栏打开,线程恢复。

需要注意的是,当指定的线程数量大于启动的线程数量,比如修改上例中的代码,只启动4个线程,那么所有的线程将一直处于等待状态。瞅下代码~

for (int i = 0; i < 4; i++) { //上面示例中是5,我们这里改成了4 service.execute(new Player("玩家" + nameList.get(0), barrier)); nameList.remove(0); }

再看下打印结果:发现只有四个玩家准备了,没有人进入游戏。而且我们的程序并没有结束(shutDown并没有执行)



第二种情况是指定的线程数量小于启动的线程,上例代码,启动6个线程,那么当第5个线程到达栅栏点时,那么这5个线程就会恢复继续执行,而第6个线程将一直处于阻塞状态。大家可以自行修改代码验证一下~ 

CyclicBarrier还提供一个更高级的构造函数CyclicBarrier(int parties, Runnable barrierAction),用于在线程到达屏障时,优先执行barrierAction,方便处理更复杂的业务场景。我们 把上面的CyclicBarrier创建代码修改成如下~

CyclicBarrier barrier = new CyclicBarrier(5, new Runnable() {
            @Override public void run() {
                System.out.println("已经到达栅栏点~");
            }
        });

打印结果是这样的:

玩家-----卡莎-----已准备,等待其他玩家准备...
玩家-----蠢劫-----已准备,等待其他玩家准备...
玩家-----剑圣-----已准备,等待其他玩家准备...
玩家-----盲僧-----已准备,等待其他玩家准备...
玩家-----风男-----已准备,等待其他玩家准备...
已经到达栅栏点~
玩家-----剑圣-----已加入游戏
玩家-----卡莎-----已加入游戏
玩家-----风男-----已加入游戏
玩家-----盲僧-----已加入游戏
玩家-----蠢劫-----已加入游戏

2常用方法介绍


await()

Waits until all parties have invoked await on this barrier.

调用该方法会使当前线程在栅栏点发生阻塞,直到指定的线程数量都达到栅栏点时恢复执行

await(long timeout, TimeUnit unit)

Waits until all parties have invoked await on this barrier, or the specified waiting time elapses.

类似于await(),增加了超时时间参数。 

参考如下代码:

public void run() {
    try {
        TimeUnit.SECONDS.sleep(1 + (new Random().nextInt(3)));
        System.out.println(name + "已准备,等待其他玩家准备...");
        barrier.await(200, TimeUnit.MILLISECONDS);
        TimeUnit.SECONDS.sleep(1 + (new Random().nextInt(3)));
        System.out.println(name + "已加入游戏");
    } catch (InterruptedException e) {
        System.out.println(name + "离开游戏");
    } catch (BrokenBarrierException e) {
        System.out.println(name + "离开游戏");
    } catch (TimeoutException e) {
        System.out.println(name + "连接超时");
    }
}

结果如下:

玩家-----剑圣-----已准备,等待其他玩家准备...
玩家-----卡莎-----已准备,等待其他玩家准备...
玩家-----剑圣-----连接超时
玩家-----卡莎-----离开游戏
玩家-----风男-----已准备,等待其他玩家准备...
玩家-----蠢劫-----已准备,等待其他玩家准备...
玩家-----蠢劫-----离开游戏
玩家-----风男-----离开游戏
玩家-----盲僧-----已准备,等待其他玩家准备...
玩家-----盲僧-----离开游戏

上面的代码修改了CyclicBarrierTest.java里面的run()方法,当barrier在等待点出等待超时时,会抛出TimeoutException异常,同时,位于该barrier上的其他线程也将毁抛出BrokenBarrierException异常。这里说明,barrier上的线程要么同时成功要么同时失败,不存在部分成功部分失败的场景。

getNumberWaiting()

Returns the number of parties currently waiting at the barrier.

返回当前在栅栏处等待的参与者数目。此方法主要用于调试和断言。

getParties()

Returns the number of parties required to trip this barrier.

该方法可以获得构造函数中指定的需要在栅栏点阻塞的线程数量

isBroken()

Queries if this barrier is in a broken state

查询此栅栏是否处于损坏状态。

reset()

Resets the barrier to its initial state.

将barrier重置为其初始状态。如果所有参与者目前都在屏障处等待,则它们将返回,同时抛出一个 BrokenBarrierException


3应用场景


CyclicBarrier可以用于多线程计算数据,最后合并计算结果的应用场景。比如我们用一个Excel保存了用户所有银行流水,每个Sheet保存一个帐户近一年的每笔银行流水,现在需要统计用户的日均银行流水,先用多线程处理每个sheet里的银行流水,都执行完之后,得到每个sheet的日均银行流水,最后,再用barrierAction用这些线程的计算结果,计算出整个Excel的日均银行流水


参考:

http://ifeve.com/concurrency-cyclicbarrier/ 

https://blog.csdn.net/yin380697242/article/details/53313622