Java多线程:回环屏障CyclicBarrier的使用(八)

导读:本篇文章讲解 Java多线程:回环屏障CyclicBarrier的使用(八),希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

CyclicBarrier(回环屏障)

CyclicBarrier阻塞一组线程,直至某个状态之后再全部同时执行,并且所有线程都被释放。和之前说的CountdownLatch功能相反,CountdownLatch阻塞主线程,等所有子线程完结了再继续下去
上节介绍的CountDownLatch在解决多个线程同步方面相对于调用线程的join方法已经有了不少优化,但是CountDownLatch的计数器是一次性的,也就是等到计数器值变为0后,再调用CountDownIatch的await和countdown方法都会立刻返回,这就起不到线程同步的效果了。所以为了满足计数器可以重置的需要,JDK开发组提供了CyclicBarrier类,并且CyclicBarrier类的功能并不限于CountDownLatch的功能。从字面意思理解,CyclicBarrier是回环屏障的意思,可以让一组线程全部达到一个状态后再全部同时执行
这里之所以叫作回环是因为当所有等待线程执行完毕,并重置CyclicBarrier的状态后它可以被重用之所以叫作屏障是因为线程调用await方法后就会被阻塞,这个阻塞点就称为屏障点,等所有线程都调用了await 方法后,线程就会冲破屏障,继续向下运行
示例:

public class CycleBarrierTest1 {

    private static CyclicBarrier cyclicBarrier = new CyclicBarrier(2, new Runnable() {
        @Override
        public void run() {
            System.out.println(Thread.currentThread()+"task1 merge result");
        }
    });

    public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(2);
        service.submit(new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println(Thread.currentThread()+"task1-1");
                    System.out.println(Thread.currentThread()+"enter in barrier");
                    cyclicBarrier.await();
                    System.out.println(Thread.currentThread()+"enter out barrier");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });

        service.submit(new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println(Thread.currentThread()+"task1-2");
                    System.out.println(Thread.currentThread()+"enter in barrier");
                    cyclicBarrier.await();
                    System.out.println(Thread.currentThread()+"enter out barrier");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
        service.shutdown();
    }
}

运行的结果如下
在这里插入图片描述
如上代码创建了一个CyclicBarrier对象,第一个参数计数器初始值第二个参数Runable是当计数器值为0时需要执行的任务。在main函数里面首先创建了一个大小为2的线程池,然后添加两个子任务到线程池,每个子任务在执行完自己的逻辑后会调用await()方法。一开始计数器值为2,当第一个线程调用await方法时,计数器值会递减为1。由于此时计数器值不为0,所以当前线程就到了屏障点而被阻塞。然后第二个线程调用await时,会进入屏障,计数器值也会递减,现在计数器值为0,这时就会去执行CyclicBarrier构造函数中的任务,执行完毕后退出屏障点,并且唤醒被阻塞的第二个线程,这时候第一个线程也会退出屏障点继续向下运行。上面的例子说明了多个线程之间是相互等待的,假如计数器值为N,那么随后调用await方法的N-1个线程都会因为到达屏障点而被阻塞,当第N个线程调用await后,计数器值为0了,这时候第N个线程才会发出通知唤醒前面的N-1个线程。也就是当全部线程都到达屏障点时才能一块继续向下执行

下面再举个例子来说明CyclicBarrier的可复用性。假设一个任务由阶段1、阶段2和阶段3组成,每个线程要串行地执行阶段1、阶段2和阶段3,当多个线程执行该任务时,必须要保证所有线程的阶段1全部完成后才能进入阶段2执行,当所有线程的阶段2全部完成后才能进入阶段3执行。

示例:

public class CycleBarrierTest2 {

    private static CyclicBarrier cyclicBarrier = new CyclicBarrier(2);

    public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(2);
        service.submit(new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println(Thread.currentThread()+"step-1");
                    cyclicBarrier.await();
                    System.out.println(Thread.currentThread()+"step-2");
                    cyclicBarrier.await();
                    System.out.println(Thread.currentThread()+"step-3");

                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });

        service.submit(new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println(Thread.currentThread()+"step-1");
                    cyclicBarrier.await();
                    System.out.println(Thread.currentThread()+"step-2");
                    cyclicBarrier.await();
                    System.out.println(Thread.currentThread()+"step-3");

                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
        service.shutdown();
    }
}

在这里插入图片描述
在如上代码中,每个子线程在执行完阶段1后都调用了await方法,等到所有线程都到达屏障点后才会-块往下执行,这就保证了所有线程都完成了阶段1后才会开始执行阶段2。然后在阶段2后面调用了await方法,这保证了所有线程都完成了阶段2后,才能开始阶段3的执行。这个功能使用单个CountDownLatch是无法完成的。

这里要注意,程序在执行的时候,只有达到了计数器的值等于0,才会执行,否则会挂起,比如计数器的值为2,但只调用了一个await()方法(计数器减一),此时的计数器为1,如果计数器不为0,那么程序将会阻塞挂起等待。将上面示例中的cyclicBarrier.await();注释一个,那么程序将挂起等待。
在这里插入图片描述
用cyclicBarrier模拟起跑

public class CyclicBarrierTest {
    // TODO 发令枪数量设置
    private static final int N = 10;
    //  创建一个回环屏障
    private static CyclicBarrier cyclicBarrier = new CyclicBarrier(N, new Runnable() {
        @Override
        public void run() {
            System.out.println(Thread.currentThread() + "枪响了,开跑");
        }
    });

    public static void main(String rgs[]) throws InterruptedException {
        //发令枪测试
        ExecutorService service = Executors.newCachedThreadPool();
        System.out.println("运动员上跑道准备。。。。");
        for (int i = 0; i < N; ++i) // create and start threads
            //任务
            service.execute(new WorkerRunnable(cyclicBarrier, i));
        service.shutdown();
    }
}

class WorkerRunnable implements Runnable {
    private final CyclicBarrier cb;
    private final int i;

    WorkerRunnable(CyclicBarrier cb, int i) {
        this.cb = cb;
        this.i = i;
    }

    public void run() {
        try {
            //阻塞等待,各就位等待号令枪响开始跑
            System.out.println(i + "---> 准备就绪,等待开跑");
            cb.await();
            //业务代码
            doWork(i);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }
    }

    // TODO 其他业务测试在此方法内修改代码即可
    void doWork(int i) {
        System.out.println(i + "---> 起跑逻辑代码");
    }
}

在这里插入图片描述

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/77198.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!