首页 > 其他分享 >面试官:实战中用过CountDownLatch吗?详细说一说,我:啊这

面试官:实战中用过CountDownLatch吗?详细说一说,我:啊这

时间:2024-04-14 15:23:49浏览次数:20  
标签:面试官 说一说 Thread await 线程 CountDownLatch new public

写在开头

在很多的面经中都看到过提问 CountDownLatch 的问题,正好我们最近也在梳理学习AQS(抽象队列同步器),而CountDownLatch又是其中典型的代表,我们今天就继续来学一下这个同步工具类!

CountDownLatch有何作用?

我们知道AQS是专属于构造锁和同步器的一个抽象工具类,基于它Java构造出了大量的常用同步工具,如ReentrantLock、Semaphore、ReentrantReadWriteLock、SynchronousQueue等等,我们今天的主角CountDownLatch同样如此。

CountDownLatch(倒时器)允许N个线程阻塞在同一个地方,直至所有线程的任务都执行完毕。CountDownLatch 有一个计数器,可以通过countDown()方法对计数器的数目进行减一操作,也可以通过await()方法来阻塞当前线程,直到计数器的值为 0。

CountDownLatch的底层原理

想要迅速了解一个Java类的内部构造,或者使用原理,最快速直接的办法就是看它的源码,这是很多初学者比较抵触的,会觉得很多封装起来的源码都晦涩难懂,诚然很多类内部实现是复杂,但我们作为Java工程师也不能只追求CRUD呀,培养自己看源码的习惯,硬着头皮看段时间,代码能力绝对会提升的!

废话说的有点多了,我们直接进入CountDownLatch内部去看看它的底层原理吧

【源码解析1】

//几乎所有基于AQS构造的同步类,内部都需要一个静态内部类去继承AQS
private static final class Sync extends AbstractQueuedSynchronizer {
     private static final long serialVersionUID = 4982264981922014374L;
     Sync(int count) {
         setState(count);
     }
     int getCount() {
         return getState();
     }
 }
private final Sync sync;
//构造方法中初始化count值
public CountDownLatch(int count) {
    if (count < 0) throw new IllegalArgumentException("count < 0");
    this.sync = new Sync(count);
}

几乎所有基于AQS构造的同步类,内部都需要一个静态内部类去继承AQS,并实现其提供的钩子方法,通过封装AQS中的state为count来确定多个线程的计时器。

countDown()方法

【源码解析2】

//核心方法,内部封装了共享模式下的线程释放
 public void countDown() {
 	//内部类Sync,继承了AQS
     sync.releaseShared(1);
 }
 //AQS内部的实现
 public final boolean releaseShared(int arg) {
  if (tryReleaseShared(arg)) {
  		//唤醒后继节点
         doReleaseShared();
         return true;
     }
     return false;
 }   

在CountDownLatch中通过countDown来减少倒计时数,这是最重要的一个方法,我们继续跟进源码看到它通过releaseShared()方法去释放锁,这个方法是AQS内部的默认实现方法,而在这个方法中再一次的调用了tryReleaseShared(arg),这是一个AQS的钩子方法,方法内部仅有默认的异常处理,真正的实现由CountDownLatch内部类Sync完成

image

【源码解析3】

// 对 state 进行递减,直到 state 变成 0;
// 只有 count 递减到 0 时,countDown 才会返回 true
protected boolean tryReleaseShared(int releases) {
    // 自选检查 state 是否为 0
    for (;;) {
        int c = getState();
        // 如果 state 已经是 0 了,直接返回 false
        if (c == 0)
            return false;
        // 对 state 进行递减
        int nextc = c-1;
        // CAS 操作更新 state 的值
        if (compareAndSetState(c, nextc))
            return nextc == 0;
    }
}

await()方法

除了countDown()方法外,在CountDownLatch中还有一个重要方法就是 await ,在多线程环境下,线程的执行顺序并不一致,因此,对于一个倒时器也说,先开始的线程应该阻塞等待直至最后一个线程执行完成,而实现这一效果的就是await()方法!

【源码解析4】

// 等待(也可以叫做加锁)
public void await() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}
// 带有超时时间的等待
public boolean await(long timeout, TimeUnit unit)
    throws InterruptedException {
    return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}

其中await()方法可以配置带有时间参数的,表示最大阻塞时间,当调用 await() 的时候,如果 state 不为 0,那就证明任务还没有执行完毕,await() 就会一直阻塞,也就是说 await() 之后的语句不会被执行。然后,CountDownLatch 会自旋 CAS 判断 state是否等于0,若是就会释放所有等待的线程,await() 方法之后的语句得到执行。

CountDownLatch的使用

由于await的实现步骤和countDown类似,我们就不贴源码了,大家自己跟进去也很容易看明白,我们现在直接来一个小demo感受一下如何使用CountDownLatch做一个倒时器

【代码样例1】

public class Test {
    public static void main(String[] args) throws InterruptedException {
        // 创建一个倒计数为 3 的 CountDownLatch
        CountDownLatch latch = new CountDownLatch(3);

        Thread service1 = new Thread(new Service("3", 1000, latch));
        Thread service2 = new Thread(new Service("2", 2000, latch));
        Thread service3 = new Thread(new Service("1", 3000, latch));

        service1.start();
        service2.start();
        service3.start();

        // 等待所有服务初始化完成
        latch.await();
        System.out.println("发射");
    }

    static class Service implements Runnable {
        private final String name;
        private final int timeToStart;
        private final CountDownLatch latch;

        public Service(String name, int timeToStart, CountDownLatch latch) {
            this.name = name;
            this.timeToStart = timeToStart;
            this.latch = latch;
        }

        @Override
        public void run() {
            try {
                Thread.sleep(timeToStart);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(name);
            // 减少倒计数
            latch.countDown();
        }
    }
}

输出:

3
2
1
发射

执行结果体现出了倒计时的效果每隔1秒进行3,2,1的倒数;其实除了倒计时器外CountDownLatch还有另外一个使用场景:实现多个线程开始执行任务的最大并行性

多个线程在某一时刻同时开始执行。类似于赛跑,将多个线程放到起点,等待发令枪响,然后同时开跑。

具体做法是: 初始化一个共享的 CountDownLatch 对象,将其计数器初始化为 1 (new CountDownLatch(1)),多个线程在开始执行任务前首先 coundownlatch.await(),当主线程调用 countDown() 时,计数器变为 0,多个线程同时被唤醒。

【代码样例2】

public class Test {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(1);
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                try {
                    System.out.println("5位运动员就位!");
                    //等待发令枪响
                    countDownLatch.await();

                    System.out.println(Thread.currentThread().getName() + "起跑!");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }
        // 裁判准备发令
        Thread.sleep(2000);
        //发令枪响
        countDownLatch.countDown();
    }
}

输出:

5位运动员就位!
5位运动员就位!
5位运动员就位!
5位运动员就位!
5位运动员就位!
Thread-0起跑!
Thread-3起跑!
Thread-4起跑!
Thread-1起跑!
Thread-2起跑!

结尾彩蛋

如果本篇博客对您有一定的帮助,大家记得留言+点赞+收藏呀。原创不易,转载请联系Build哥!

image

如果您想与Build哥的关系更近一步,还可以关注“JavaBuild888”,在这里除了看到《Java成长计划》系列博文,还有提升工作效率的小笔记、读书心得、大厂面经、人生感悟等等,欢迎您的加入!

image

标签:面试官,说一说,Thread,await,线程,CountDownLatch,new,public
From: https://www.cnblogs.com/JavaBuild/p/18134174

相关文章

  • 并发同步计数器 CountDownLatch
    概念解释CountDownLatch是Java中的一个同步辅助类,它可以让一个或多个线程等待其他线程完成操作后再继续执行。CountDownLatch内部维护了一个计数器,当计数器的值减为0时,所有等待的线程就会被唤醒。 CountDownLatch的主要方法包括:构造方法:CountDownLatch(intcount)构......
  • 美团一面,面试官让介绍AQS原理并手写一个同步器,直接凉了
    写在开头今天在牛客上看到了一个帖子,一个网友吐槽美团一面上来就让手撕同步器,没整出来,结果面试直接凉凉。就此联想到一周前写的一篇关于AQS知识点解析的博文,当时也曾埋下伏笔说后面会根据AQS的原理实现一个自定义的同步器,那今天就来把这个坑给填上哈。常用的AQS架构同步器类......
  • 并发工具类:ExecutorService、Future、CountDownLatch与Semaphore(第一章)
    目录一、引言ExecutorService与Future:优雅的任务提交与结果获取CountDownLatch:精确的线程同步点Semaphore:资源访问的流量控制器总结二、ExecutorService定义与接口概述生命周期管理高级特性与最佳实践使用ExecutorService时的常见注意事项与最佳实践建议一、引言......
  • 面试官:Zookeeper是什么,它有什么特性与使用场景?
    一、前言作为一名Java程序员,Zookeeper底层的一些原理是我们不必学会就可以搬砖工作的一种技能点,但是小奇为什么还要讲一下呢?难道就是为了浪费大家1分钟的宝贵时间,一个人1分钟,50万人就是1年,5000万人就是100年,赚了,小奇以一己之力成功搞挂一个人(血赚)。当然不是,并且小奇的文章......
  • 《架构风清扬-Java面试系列第13讲》说一说Java对象在内存中的生命周期
    大家好,加个餐!像线程的生命周期,Servlet的生命周期,相信这类问题大家都非常熟悉了Java对象在内存中的生命周期,这个题目倒是有些新鲜来,思考片刻,说出你的答案(PS:上图缓冲)Java对象在其内存中的生命周期可以被划分为多个阶段,下面钊哥逐个给大家说一说1,创建阶段(Creation......
  • 【吊打面试官系列】Redis篇 -都有哪些办法可以降低 Redis 的内存使用情况呢?
    大家好,我是锋哥。今天分享关于【都有哪些办法可以降低Redis的内存使用情况呢?】面试题,希望对大家有帮助;都有哪些办法可以降低Redis的内存使用情况呢?如果你使用的是32位的Redis实例,可以好好利用Hash,list,sortedset,set等集合类型数据,因为通常情况下很多小的Key......
  • Spring Boot 获取 bean 的 3 种方式!还有谁不会?,Java面试官
    @AutowiredprivatestaticAutoMethodDemoServicestaticAutoMethodDemoService;@PostConstructpublicvoidinit(){staticAutoMethodDemoService=autoMethodDemoService;}publicstaticStringgetAuthorizer(){returnstaticAutoMethodDemoService.test();}}......
  • 面试官:如何实现10亿数据判重?
    当数据量比较大时,使用常规的方式来判重就不行了。例如,使用MySQL数据库判重,或使用List.contains()或Set.contains()判重就不可行,因为MySQL在数据量大时查询就会非常慢,而数据库又是及其珍贵的全局数据库资源。《阿里巴巴Java开发手册》上也说了,如果单表数据量超过5......
  • 面试官:说说Spring中IoC实现原理?
    IoC(InversionofControl)即控制(权)反转,它是一种编程思想,它的核心理念是将对象的创建和管理权力从对象本身转移到外部的容器或框架。IoC的主要目的是降低代码之间的耦合度,提高代码的重用性、可测试性和灵活性。在IoC模式下,对象不需要自己创建或者查找它们所依赖的对象,这些工作由......
  • 说一说视频监控领域常见的一些性能指标
    常见的主码流、子码流是的码率大概是多少?安防领域,如果是H264的摄像机,一般主码流是4Mbps,子码流一般是1M,2M,甚至512K的都有。这些都是可设定的。如果是265编码的摄像机,可能主码流一般是2Mbps,子码流是1Mbps.bps的概念不是字节,而是位的概念。需要注意的是,即便是2M的主码流,可能......