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

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 + "---> 起跑逻辑代码");
    }
}

在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值