温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

怎么解析JUC 下CountDownLatch,CyclicBarrier,Semaphore

发布时间:2021-12-20 09:24:39 阅读:151 作者:柒染 栏目:大数据
开发者测试专用服务器限时活动,0元免费领,库存有限,领完即止! 点击查看>>

怎么解析JUC 下CountDownLatch,CyclicBarrier,Semaphore,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。

在 JUC 下包含了一些常用的同步工具类,今天就来详细介绍一下,CountDownLatch,CyclicBarrier,Semaphore 的使用方法以及它们之间的区别。

一、CountDownLatch

先看一下,CountDownLatch 源码的官方介绍。

怎么解析JUC 下CountDownLatch,CyclicBarrier,Semaphore  

意思是,它是一个同步辅助器,允许一个或多个线程一直等待,直到一组在其他线程执行的操作全部完成。

public CountDownLatch(int count) {	if (count < 0) throw new IllegalArgumentException("count < 0");	this.sync = new Sync(count);}
 

它的构造方法,会传入一个 count 值,用于计数。

常用的方法有两个:

public void await() throws InterruptedException {	sync.acquireSharedInterruptibly(1);}public void countDown() {	sync.releaseShared(1);}
 

当一个线程调用await方法时,就会阻塞当前线程。每当有线程调用一次 countDown 方法时,计数就会减 1。当 count 的值等于 0 的时候,被阻塞的线程才会继续运行。

现在设想一个场景,公司项目,线上出现了一个紧急 bug,被客户投诉,领导焦急的过来,想找人迅速的解决这个 bug 。

那么,一个人解决肯定速度慢啊,于是叫来张三和李四,一起分工解决。终于,当他们两个都做完了自己所需要做的任务之后,领导才可以答复客户,客户也就消气了(没办法啊,客户是上帝嘛)。

于是,我们可以设计一个 Worker 类来模拟单个人修复 bug 的过程,主线程就是领导,一直等待所有 Worker 任务执行结束,主线程才可以继续往下走。

public class CountDownTest {    static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");    public static void main(String[] args) throws InterruptedException {        CountDownLatch latch = new CountDownLatch(2);        Worker w1 = new Worker("张三", 2000, latch);        Worker w2 = new Worker("李四", 3000, latch);        w1.start();        w2.start();        long startTime = System.currentTimeMillis();        latch.await();        System.out.println("bug全部解决,领导可以给客户交差了,任务总耗时:"+ (System.currentTimeMillis() - startTime));    }    static class Worker extends Thread{        String name;        int workTime;        CountDownLatch latch;        public Worker(String name, int workTime, CountDownLatch latch) {            this.name = name;            this.workTime = workTime;            this.latch = latch;        }        @Override        public void run() {            System.out.println(name+"开始修复bug,当前时间:"+sdf.format(new Date()));            doWork();            System.out.println(name+"结束修复bug,当前时间:"+sdf.format(new Date()));            latch.countDown();        }        private void doWork() {            try {                //模拟工作耗时                Thread.sleep(workTime);            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }}
 

本来需要 5 秒完成的任务,两个人 3 秒就完成了。我只能说,这程序员的工作效率真是太太太高了。

 

二、CyclicBarrier

barrier 英文是屏障,障碍,栅栏的意思。cyclic是循环的意思,就是说,这个屏障可以循环使用(什么意思,等下我举例子就知道了)。源码官方解释是:

A synchronization aid that allows a set of threads to all wait for each other to reach a common barrier point . The barrier is called cyclic because it can be re-used after the waiting threads are released.

一组线程会互相等待,直到所有线程都到达一个同步点。这个就非常有意思了,就像一群人被困到了一个栅栏前面,只有等最后一个人到达之后,他们才可以合力把栅栏(屏障)突破。

CyclicBarrier 提供了两种构造方法:

public CyclicBarrier(int parties) {    this(parties, null);}public CyclicBarrier(int parties, Runnable barrierAction) {    if (parties <= 0) throw new IllegalArgumentException();    this.parties = parties;    this.count = parties;    this.barrierCommand = barrierAction;}
 

第一个构造的参数,指的是需要几个线程一起到达,才可以使所有线程取消等待。第二个构造,额外指定了一个参数,用于在所有线程达到屏障时,优先执行 barrierAction。

现在模拟一个常用的场景,一组运动员比赛 1000 米,只有在所有人都准备完成之后,才可以一起开跑(额,先忽略裁判吹口哨的细节)。

定义一个 Runner 类代表运动员,其内部维护一个共有的 CyclicBarrier,每个人都有一个准备时间,准备完成之后,会调用 await 方法,等待其他运动员。当所有人准备都 OK 时,就可以开跑了。

public class BarrierTest {    public static void main(String[] args) {        CyclicBarrier barrier = new CyclicBarrier(3);  //①        Runner runner1 = new Runner(barrier, "张三");        Runner runner2 = new Runner(barrier, "李四");        Runner runner3 = new Runner(barrier, "王五");        ExecutorService service = Executors.newFixedThreadPool(3);        service.execute(runner1);        service.execute(runner2);        service.execute(runner3);        service.shutdown();    }}class Runner implements Runnable{    private CyclicBarrier barrier;    private String name;    public Runner(CyclicBarrier barrier, String name) {        this.barrier = barrier;        this.name = name;    }    @Override    public void run() {        try {            //模拟准备耗时            Thread.sleep(new Random().nextInt(5000));            System.out.println(name + ":准备OK");            barrier.await();            System.out.println(name +": 开跑");        } catch (InterruptedException e) {            e.printStackTrace();        } catch (BrokenBarrierException e){            e.printStackTrace();        }    }}
 

可以看到,我们已经实现了需要的功能。但是,有的同学就较真了,说你这不行啊,哪有运动员都准备好之后就开跑的,你还把裁判放在眼里吗,裁判不吹口哨,你敢跑一个试试。

好吧,也确实是这样一个理儿,那么,我们就实现一下,让裁判吹完口哨之后,他们再一起开跑吧。

这里就要用到第二个构造函数了,于是我把代码 ① 处稍微修改一下。

CyclicBarrier barrier = new CyclicBarrier(3, new Runnable() {    @Override    public void run() {        try {            System.out.println("等裁判吹口哨...");            //这里停顿两秒更便于观察线程执行的先后顺序            Thread.sleep(2000);            System.out.println("裁判吹口哨->>>>>");        } catch (InterruptedException e) {            e.printStackTrace();        }    }});
 

执行结果:

张三:准备OK李四:准备OK王五:准备OK等裁判吹口哨...裁判吹口哨->>>>>张三: 开跑李四: 开跑王五: 开跑
 

可以看到,虽然三个人都已经准备 OK了,但是,只有裁判吹完口哨之后,他们才可以开跑。

刚才,提到了循环利用是怎么体现的呢。我现在把屏障值改为 2,然后增加一个“赵六” 一起参与赛跑。被修改的部分如下:

怎么解析JUC 下CountDownLatch,CyclicBarrier,Semaphore  

此时观察,打印结果:

张三:准备OK李四:准备OK等裁判吹口哨...裁判吹口哨->>>>>李四: 开跑张三: 开跑王五:准备OK赵六:准备OK等裁判吹口哨...裁判吹口哨->>>>>赵六: 开跑王五: 开跑
 

发现没,可以分两批,第一批先跑两个人,然后第二批再跑两个人。也就实现了屏障的循环使用。

 

三、Semaphore

Semaphore 信号量,用来控制同一时间,资源可被访问的线程数量,一般可用于流量的控制。

打个比方,现在有一段公路交通比较拥堵,那怎么办呢。此时,就需要警察叔叔出面,限制车的流量。

比如,现在有 20 辆车要通过这个地段, 警察叔叔规定同一时间,最多只能通过 5 辆车,其他车辆只能等待。只有拿到许可的车辆可通过,等车辆通过之后,再归还许可,然后把它发给等待的车辆,获得许可的车辆再通行,依次类推。

public class SemaphoreTest {    private static int count = 20;    public static void main(String[] args) {        ExecutorService executorService = Executors.newFixedThreadPool(count);        //指定最多只能有五个线程同时执行        Semaphore semaphore = new Semaphore(5);        Random random = new Random();        for (int i = 0; i < count; i++) {            final int no = i;            executorService.execute(new Runnable() {                @Override                public void run() {                    try {                        //获得许可                        semaphore.acquire();                        System.out.println(no +":号车可通行");                        //模拟车辆通行耗时                        Thread.sleep(random.nextInt(2000));                        //释放许可                        semaphore.release();                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                }            });        }        executorService.shutdown();    }}
 

打印结果我就不写了,需要读者自行观察,就会发现,第一批是五个车同时通行。然后,后边的车才可以依次通行,但是同时通行的车辆不能超过 5 辆。

细心的读者,就会发现,这许可一共就发 5 个,那等第一批车辆用完释放之后, 第二批的时候应该发给谁呢?

这确实是一个问题。所有等待的车辆都想先拿到许可,先通行,怎么办。这就需要,用到锁了。就所有人都去抢,谁先抢到,谁就先走呗。

我们去看一下 Semaphore的构造函数,就会发现,可以传入一个 boolean 值的参数,控制抢锁是否是公平的。

public Semaphore(int permits) {    sync = new NonfairSync(permits);}public Semaphore(int permits, boolean fair) {    sync = fair ? new FairSync(permits) : new NonfairSync(permits);}
 

默认是非公平,可以传入 true 来使用公平锁。(锁的机制是通过AQS)

  1. CountDownLatch 是一个线程等待其他线程, CyclicBarrier 是多个线程互相等待。
  2. CountDownLatch 的计数是减 1 直到 0,CyclicBarrier 是加 1,直到指定值。
  3. CountDownLatch 是一次性的, CyclicBarrier  可以循环利用。
  4. CyclicBarrier 可以在最后一个线程达到屏障之前,选择先执行一个操作。
  5. Semaphore ,需要拿到许可才能执行,并可以选择公平和非公平模式。

关于怎么解析JUC 下CountDownLatch,CyclicBarrier,Semaphore问题的解答就分享到这里了,希望以上内容可以对大家有一定的帮助,如果你还有很多疑惑没有解开,可以关注亿速云行业资讯频道了解更多相关知识。

亿速云「云服务器」,即开即用、新一代英特尔至强铂金CPU、三副本存储NVMe SSD云盘,价格低至29元/月。点击查看>>

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

原文链接:https://my.oschina.net/u/4606167/blog/4480350

AI

开发者交流群×