CountDownLatch 和 CyclicBarrier 的基本使用

Posted hanganglin

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CountDownLatch 和 CyclicBarrier 的基本使用相关的知识,希望对你有一定的参考价值。

  CountDownLatch 和 CyclicBarrier 是并发编程中常用的辅助类,两者使用上有点类似,但又有不同。

一、CountDownLatch

  CountDownLatch 可是实现类似计数器的功能,比如一个线程 A 需要等待其余多个任务执行完毕后才能执行,此时可以使用这个工具类。 

  构造器:

public CountDownLatch(int count) { }

  主要方法:

public void await() throws InterruptedException { };   // 调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行
public boolean await(long timeout, TimeUnit unit) throws InterruptedException { };  // 和await()类似,只不过等待一定的时间后count值还没变为0的话就会继续执行
public void countDown() { };  // 将count值减1

  示例方法:

技术分享图片
public static void main(String[] args) {

    final CountDownLatch latch = new CountDownLatch(2);

    new Thread() {
        @Override
        public void run() {
            try {
                System.out.println("子线程" + Thread.currentThread().getName() + "正在执行");
                Thread.sleep(3000);
                System.out.println("子线程" + Thread.currentThread().getName() + "执行完毕");
                latch.countDown();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }.start();

    new Thread() {
        @Override
        public void run() {
            try {
                System.out.println("子线程" + Thread.currentThread().getName() + "正在执行");
                Thread.sleep(3000);
                System.out.println("子线程" + Thread.currentThread().getName() + "执行完毕");
                latch.countDown();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }.start();

    try {
        System.out.println("等待2个子线程执行完毕...");
        latch.await();
        System.out.println("2个子线程已经执行完毕");
        System.out.println("继续执行主线程");
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}
View Code

  执行结果: 

子线程Thread-0正在执行
等待2个子线程执行完毕...
子线程Thread-1正在执行
子线程Thread-1执行完毕
子线程Thread-0执行完毕
2个子线程已经执行完毕
继续执行主线程
二、CyclicBarrier

  CyclicBarrier 可以称为回环栅,可以实现所有线程同时执行某动作的效果。比如跑步运动员在比赛前需要进行准备工作,等所有运动员都准备完毕后,同时开始比赛。

  构造器:

public CyclicBarrier(int parties) { }  // 设置线程数量
public CyclicBarrier(int parties, Runnable barrierAction) { }  // 设置线程数量,并设置所有线程 await 执行完毕后的方法
 

  主要方法:

public int await() throws InterruptedException, BrokenBarrierException { };  // 挂起线程,直至所有线程达到 barrier 状态再执行
public int await(long timeout, TimeUnit unit)throws InterruptedException,BrokenBarrierException,TimeoutException { };  // 同上,挂起线程,等待一定的时间

   示例方法:

技术分享图片
public class CyclicBarrierTest {

    public static void main(String[] args) {
        CyclicBarrier barrier = new CyclicBarrier(3, new Runnable() {
            @Override
            public void run() {
                System.out.println("所有运动员准备完毕,倒计时3秒后比赛开始...我是裁判:" + Thread.currentThread().getName()
                    + ",当前时间:" + System.currentTimeMillis());
            }
        });

        new Competition(barrier, "张三").start();
        new Competition(barrier, "李四").start();
        new Competition(barrier, "王五").start();
    }

    /**
     * 跑步比赛内容,所有人准备工作完成后,起跑
     */
    static class Competition extends Thread {

        private CyclicBarrier cyclicBarrier;

        private String name;

        public Competition(CyclicBarrier cyclicBarrier, String name) {
            this.cyclicBarrier = cyclicBarrier;
            this.name = name;
        }

        @Override
        public void run() {
            int time = new Random().nextInt(10);
            System.out.println(name + "开始准备工作,预计耗时:" + time + "秒");
            try {
                // 准备中
                Thread.sleep(time * 1000);
                System.out.println(name + "准备完成,等待其他运动员完成");
                cyclicBarrier.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
            // 起跑倒计时
            try {
                Thread.sleep(3000);
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println(name + "已经起跑...当前时间:" + System.currentTimeMillis());
        }
    }

}
View Code

  执行结果:

张三开始准备工作,预计耗时:5秒
王五开始准备工作,预计耗时:2秒
李四开始准备工作,预计耗时:6秒
王五准备完成,等待其他运动员完成
张三准备完成,等待其他运动员完成
李四准备完成,等待其他运动员完成
所有运动员准备完毕,倒计时3秒后比赛开始...我是裁判:Thread-1,当前时间:1548768695636
张三已经起跑...当前时间:1548768698637
王五已经起跑...当前时间:1548768698637
李四已经起跑...当前时间:1548768698637

  执行分析:

  三个运动员每个人是一个线程,执行比赛这一个动作。准备工作和起跑是比赛动作的两个部分,其中准备工作耗时不同,起跑动作又需要所有运动员都准备完毕才可以进行。线程启动后,每个运动员执行自身的准备工作,然后阻塞等待其余所有线程执行完准备工作(执行完 await 前序动作),再同时执行各自线程的剩余工作(起跑)。

技术分享图片

图1 CyclicBarrier 执行过程分析

三、两者区别

1、CountDownLatch 不可重置,无法重用;CyclicBarrier 可以重置,允许重复使用。

2、CountDownLatch 等待对象是一个线程等待多个线程执行完毕后,再自身执行,而 CyclicBarrier 是多个线程之间相互等待,等所有线程执行到统一状态时,再同时执行后续动作。   

 



以上是关于CountDownLatch 和 CyclicBarrier 的基本使用的主要内容,如果未能解决你的问题,请参考以下文章

基于 AQS 的并发编程: CountDownLatch 和 semaphore

CountDownLatch 和 CyclicBarrier 的基本使用

CountDownLatch CyclicBarrier和Semaphore

AQS同步组件-CountDownLatch解析和案例

CountDownLatch和CyclicBarrier的比较

Java CountDownLatch解析(下)