Java多线程/并发24、Countdownlatch应用以及与CyclicBarrier的区别

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/soonfly/article/details/71330742

Countdownlatch应用

有时候会有这样的需求:多个线程同时工作,其中几个可以随意的并发执行,但有一个线程需要等其他线程工作结束后,才能运行。举个例子,我们知道的迅雷下载,会同时开启多个线程分块下载一个大文件,每个线程下载固定的一段,最后由另外一个线程校验并拼接这些分段。这种场景可使用CountDownLatch来控制并发的执行顺序。

Countdownlatch 是一个倒计数器锁。调用CountDownLatch对象的await()方法使线程处于等待状态,调用countDown()方法的线程会将计数器减1,当计数到达0时,所有等待线程(可多个,但通常的应用场景中只有一个等待者)开始继续执行。

这里还是用F1举例:
F1赛车每次进站后,车队技师都需要在尽可能短的时间内对赛车做三个工作:加注燃油、更换轮胎、更换刹车片。当然,这三项工作都是同时进行的。只有当这三项工作完成,赛车才能驶出维修站。

public class CountdownlatchDemo {

    /**
     * @param args
     * @throws InterruptedException 
     */
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch cDownLatch=new CountDownLatch(3);//初始计数器值为3,对应3个维修组

        /*1、赛车进站*/
        System.out.println("F1赛车进站,时间:"+Calendar.getInstance().get(Calendar.SECOND));

        /*2、三个维修组同时开始对赛车维护*/
        List<mechanician> mechanician_team=new ArrayList<mechanician>();
        mechanician_team.add(new mechanician("加注燃油", cDownLatch));
        mechanician_team.add(new mechanician("更换轮胎", cDownLatch));
        mechanician_team.add(new mechanician("更换刹车片", cDownLatch));
        for(mechanician mec:mechanician_team){
            new Thread(mec).start();
        }

        /*3、等待技师完成三项工作。实际就是等待cDownLatch计数器变成0*/
        cDownLatch.await();

        /*4、完成维护,出发*/
        System.out.println("F1赛车维修完毕,出发!时间:"+Calendar.getInstance().get(Calendar.SECOND));
    }

    /*维修技师类*/
    static class mechanician implements Runnable{
        String work;
        CountDownLatch cDownLatch;
        public mechanician(String work,CountDownLatch cDownLatch) {
            this.work = work;
            this.cDownLatch = cDownLatch;
        }
        @Override
        public void run() {
            try {
                int random=new Random().nextInt(7);
                TimeUnit.SECONDS.sleep(random);
                System.out.println(Thread.currentThread().getName()+"--- "+work+" 完成,此组耗时:"+random+"秒");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally{
                /*当前技师的任务完成,cDownLatch计算器减1
                 *通常countDown放在finally里中使用*/
                cDownLatch.countDown();
            }
        }
    }
}

运行输出:

F1赛车进站,时间:37
Thread-1--- 更换轮胎 完成,此组耗时:4秒
Thread-2--- 更换刹车片 完成,此组耗时:4秒
Thread-0--- 加注燃油 完成,此组耗时:5秒
F1赛车维修完毕,出发!时间:42

可以看到,赛车会等待所有工作完成后再出发。
如果删除 cDownLatch.await();会怎么样呢?结果是顺序乱了,赛车并没有待机械维修组完成工作就跑了。

F1赛车进站,时间:18
F1赛车维修完毕,出发!时间:18
Thread-1--- 更换轮胎 完成,此组耗时:5秒
Thread-2--- 更换刹车片 完成,此组耗时:6秒
Thread-0--- 加注燃油 完成,此组耗时:6秒

最后聊一下CountDownLatch和CyclicBarrier的区别

虽然两者在大部分情况下,可以代替使用,但他们是有区别的:
CyclicBarrier可以循环使用,而CountDownLatch只能用一次。但这并不是最主要的区别,也并不是设计这两个类的初衷。
它们主要是为了不同应用场景而设计出来的:

  • CountDownLatch应用场景:主/从任务模式。是 一个或多个线程(主任务), 等待另外N个线程(从任务)完成某个事情之后才能执行。
  • CyclicBarrier应用场景:队友模式。一组N个线程(N个队友)相互等待,任意一个线程(某个队友)没有完成任务,所有线程都等着。直到这一组所有线程的任务完成,这组中每个线程才能继续往下运行。

这样应该就清楚一点了,对于CountDownLatch来说,重点是那个“一个或多个线程(主任务)”, 是它在等待, 而另外那N的线程在把“某个事情”做完之后可以继续等待,可以终止。而对于CyclicBarrier来说,重点是N个线程,他们之间任何一个没有完成,所有的线程都必须等待。

猜你喜欢

转载自blog.csdn.net/soonfly/article/details/71330742