如何使用CyclicBarrier?

CyclicBarrier 是一种线程协作的方法,它可以让一个线程执行到某个点停止,并等待其它线程到达这个点集结完毕再继续运行。

从名字的翻译可以看出 CyclicBarrier是「篱栅」,你可以想象在某个赛道上有比赛,每场比赛需要5匹马到达篱栅才能开始,如下
如何使用CyclicBarrier?_第1张图片
从这图可以看出,有5匹马要进行比赛,但由于马5没有到达赛场,而导致比赛不能开始,下面是代码演示

public class CyclicBarrierDemo {
     

    static CyclicBarrier cyclicBarrier = new CyclicBarrier(5);

    public static void main(String[] args) {
     

        ExecutorService threadPool = Executors.newFixedThreadPool(5);

        for (int i = 0; i < 4; ++i) {
     
            threadPool.submit(new Task());
        }
        threadPool.shutdown();

    }

    static class Task implements Runnable {
     

        @Override
        public void run() {
     

            System.out.println(Thread.currentThread().getName()
                    + "已就位,等待其它马进场");
            try {
     
                cyclicBarrier.await(); //await()方法,等待设置的参数的个数的线程集结完毕
                System.out.println(Thread.currentThread().getName()
                        + "开始赛跑");
            } catch (InterruptedException e) {
     
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
     
                e.printStackTrace();
            }
        }
    }
}

如何使用CyclicBarrier?_第2张图片
从打印的结果来看,程序一直在运行,但没有结束。因为我们在上面创建 CyclicBarrier 对象的时候,传入的参数是5,也就是意味着,需要5个线程执行到调用到 await()方法的地方5次,也就是我们上面说的,需要5匹马同时到场,我们才能继续执行后面的代码,但由于我们只提交了4个任务,也就是到达了4匹马,而没有再多创建一个线程,所以这4个线程就在调用 await()方法的地方继续等待,也就是等待最后1匹马到场就可以继续执行。

当5匹马到达篱栅
如何使用CyclicBarrier?_第3张图片
所有到场的马开始奔跑
如何使用CyclicBarrier?_第4张图片
代码如下

public class CyclicBarrierDemo {
     

    static CyclicBarrier cyclicBarrier = new CyclicBarrier(5);

    public static void main(String[] args) {
     

        ExecutorService threadPool = Executors.newFixedThreadPool(5);

        for (int i = 0; i < 5; ++i) {
     
            threadPool.submit(new Task());
        }
        threadPool.shutdown();

    }

    static class Task implements Runnable {
     

        @Override
        public void run() {
     

            System.out.println(Thread.currentThread().getName()
                    + "已就位,等待其它马进场");
            try {
     
                cyclicBarrier.await(); //await()方法,等待设置的参数的个数的线程集结完毕
                System.out.println(Thread.currentThread().getName()
                        + "开始赛跑");
            } catch (InterruptedException e) {
     
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
     
                e.printStackTrace();
            }
        }
    }
}

修改的代码

for (int i = 0; i < 4; ++i)for (int i = 0; i < 5; ++i)

我们只是多创建提交了一个任务,也就是让第5匹马到达篱栅前, 就达到了比赛开始的条件,也就开始赛跑。

初始化在哪?创建 CyclicBarrier(int parties)的时候,传入的parties参数就是了。后面我们每次调用 await()方法,parties 的个数就减1,直到为0,篱栅就放开,让所有等待执行的任务继续执行。

构造器方法

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

一个参数的构造器,传入的参数,是在某些位置,需要调用 await()的次数
两个参数的构造器,我这个没用过,等以后有用过来补坑 @TODO

等待方法

int await()
int await(long timeout, TimeUnit unit)

没有带参数的,每次调用,count减1(count初始化的个数是parties的个数),直到count为0,任务就继续向下执行

带参数的 await(long timeout, TimeUnit unit),第一个参数是设置超时时间的多少,第二个参数设置的是超时时间的单位,当调用 await()的任务在等待其它任务调用 await(),促使count为0后,继续执行后面内容,但由于没有等到最后一个任务调用就超时了,这时候程序会抛出 BrokenBarrierException,并停止运行

如何使用CyclicBarrier?_第5张图片
修改代码的地方

cyclicBarrier.await() → cyclicBarrier.await(5, TimeUnit.SECONDS);
//多捕获了一个异常
catch (TimeoutException e) {
     e.printStackTrace();}

为什么是抛出 BrokenBarrierException呢?我很疑惑,我还调整了顺序,让捕获TimeoutException在代码放在前面,但还是抛出 BrokenBarrierException,很不解。这里留个坑,等以后想通了来补@TODO

查看篱栅撤掉前,需要调用 await()的次数

getParties()

查看在篱栅处等待的任务的个数

getNumberWaiting()

将篱栅重置为初始的状态

cyclicBarrier.reset();

注:同样是上面的例子,我修改了添加了一部分代码
如何使用CyclicBarrier?_第6张图片
在这里,我没有调用重置,但是可以继续执行下去,也就是说,当篱栅用过一次后,如果我们不调用 reset(),那么它会默认重置?

我看了源码之后才发现,是的,当最后一次调用了 await()方法后,进入 dowait()方法中里面的某些代码,如下一小段代码的截图

            int index = --count; //最后一次调用await()后,--count就为0了,此时的index也是0
            if (index == 0) {
       
                boolean ranAction = false;
                try {
     
                    final Runnable command = barrierCommand;
                    if (command != null) //因为我们用两个参数的构造器,所以这个 command为null,忽略这行代码
                        command.run();
                    ranAction = true;
                    nextGeneration(); //这一行是关键
                    return 0;
                } finally {
     
                    if (!ranAction)
                        breakBarrier();
                }
            }

下面是 nextGeneration() 的源码

 private void nextGeneration() {
     
        // signal completion of last generation
        trip.signalAll();
        // set up next generation
        count = parties; //它重置了count的数量,也就是说,又可以重新使用 await()方法了
        generation = new Generation();
    }

至于 reset()方法中的源码

 public void reset() {
     
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
     
            breakBarrier();   // break the current generation
            nextGeneration(); // start a new generation
        } finally {
     
            lock.unlock();
        }
    }

从上面的 nextGeneration()方法可以看出,reset()方法是重置了count,使得可以重新使用 await()方法

注:又发现了个问题,就是在多线程的情况下,你调用了 reset()方法的时候,其它线程同时也在调用 await()方法,那么这时候可能抛出BrokenBarrierException异常

public class CyclicBarrierDemo {
     

    static CyclicBarrier cyclicBarrier = new CyclicBarrier(5);

    public static void main(String[] args) throws InterruptedException {
     

        ExecutorService threadPool = Executors.newFixedThreadPool(10);

        for (int i = 0; i < 20; ++i) {
     
            threadPool.submit(new Task());
        }



        threadPool.shutdown();

    }

    static class Task implements Runnable {
     

        @Override
        public void run() {
     


            System.out.println(Thread.currentThread().getName()
                    + "已就位,等待其它马进场");
            try {
     
                cyclicBarrier.await(); 
                System.out.println(Thread.currentThread().getName()
                        + "开始赛跑");
                cyclicBarrier.reset();
            } catch (InterruptedException e) {
     
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
     
                e.printStackTrace();
            }
        }
    }
}

一部分运行结果的截图
如何使用CyclicBarrier?_第7张图片
给大家看张图就知道了

如何使用CyclicBarrier?_第8张图片
如图所示,在多线程情况下,你一个线程执行 reset()方法进入后,再进入 breakBarrier()方法中,当你把 generation.broken 赋值为 true,然后切换到其它线程,其它线程刚好调用了 await() 方法进入到 dowait()方法中,刚好判断了 generation.broken,因为前面的线程修改了它,所以 generation.broken 为trun,最后抛出了 BrokenBarrierException


欢迎大家关注下个人的「公众号」:独醉贪欢

你可能感兴趣的:(Java并发编程,CyclicBarrier,多线程,Java,线程协作)