腾讯面试居然跟我扯了半小时的CountDownLatch
一个长头发、穿着清爽的小姐姐,拿着一个崭新的Mac笔记本向我走来,看着来势汹汹,我心想着肯定是技术大佬吧!但是我也是一个才华横溢的人,稳住我们能赢。

面试官:看你简历上有写熟悉并发编程,CountDownLatch一定用过吧,跟我说说它!
我:CountDownLatch是JDK提供的一个同步工具,它可以让一个或多个线程等待,一直等到其他线程中执行完成一组操作。
面试官:CountDownLatch有哪些常用的方法?
我:有countDown方法和await方法,CountDownLatch在初始化时,需要指定用给定一个整数作为计数器。当调用countDown方法时,计数器会被减1;当调用await方法时,如果计数器大于0时,线程会被阻塞,一直到计数器被countDown方法减到0时,线程才会继续执行。计数器是无法重置的,当计数器被减到0时,调用await方法都会直接返回。
面试官:调用
countDown方法时,线程也会阻塞嘛?
我:不会的,调用countDown的线程可以继续执行,不需要等待计数器被减到0,只是调用await方法的线程需要等待。
欢迎关注微信公众号:万猫学社,每周一分享Java技术干货。
面试官:可以举一个使用CountDownLatch的例子吗?
我:比如张三、李四和王五几个人约好去饭店一起去吃饭,这几个人都是比较绅士,要等到所有人都到齐以后才让服务员上菜。这种场景就可以用到CountDownLatch。
面试官:可以写一下吗?
我:当然可以,这是张三、李四和王五的顾客类:
package onemore.study;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
public class Customer implements Runnable {
    private CountDownLatch latch;
    private String name;
    public Customer(CountDownLatch latch, String name) {
        this.latch = latch;
        this.name = name;
    }
    @Override
    public void run() {
        try {
            SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
            Random random = new Random();
            System.out.println(sdf.format(new Date()) + " " + name + "出发去饭店");
            Thread.sleep((long) (random.nextDouble() * 3000) + 1000);
            System.out.println(sdf.format(new Date()) + " " + name + "到了饭店");
            latch.countDown();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
这是服务员类:
package onemore.study;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.CountDownLatch;
public class Waitress implements Runnable {
    private CountDownLatch latch;
    private String name;
    public Waitress(CountDownLatch latch, String name) {
        this.latch = latch;
        this.name = name;
    }
    @Override
    public void run() {
        try {
            SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
            System.out.println(sdf.format(new Date()) + " " + name  + "等待顾客");
            latch.await();
            System.out.println(sdf.format(new Date()) + " " + name  + "开始上菜");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
然后,再写一个测试类,用于模拟上面所说的场景:
package onemore.study;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
public class CountDownLatchTester {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(3);
        List<Thread> threads = new ArrayList<>(3);
        threads.add(new Thread(new Customer(latch, "张三")));
        threads.add(new Thread(new Customer(latch, "李四")));
        threads.add(new Thread(new Customer(latch, "王五")));
        for (Thread thread : threads) {
            thread.start();
        }
        Thread.sleep(100);
        new Thread(new Waitress(latch, "♥小芳♥")).start();
    }
}
运行以后的结果应该是这样的:
15:25:53.015 王五出发去饭店
15:25:53.015 李四出发去饭店
15:25:53.015 张三出发去饭店
15:25:53.062 ♥小芳♥等待顾客
15:25:54.341 张三到了饭店
15:25:54.358 李四到了饭店
15:25:56.784 王五到了饭店
15:25:56.784 ♥小芳♥开始上菜
可以看到,服务员小芳在调用await方法时一直阻塞着,一直等到三个顾客都调用了countDown方法才继续执行。
欢迎关注微信公众号:万猫学社,每周一分享Java技术干货。
面试官:如果有一个顾客迟迟没到,饭店都打样了,也不能一直等啊,应该这么办?
我:可以使用await方法的另一个重载,传入等待的超时时间,比如服务员只等3秒钟,可以把服务员类中的
latch.await();
改成:
latch.await(3, TimeUnit.SECONDS);
运行结果可能是这样的:
17:24:40.915 张三出发去饭店
17:24:40.915 李四出发去饭店
17:24:40.915 王五出发去饭店
17:24:40.948 ♥小芳♥等待顾客
17:24:43.376 李四到了饭店
17:24:43.544 王五到了饭店
17:24:43.951 ♥小芳♥开始上菜
17:24:44.762 张三到了饭店
可以看到,服务员小芳在调用await方法时虽然被阻塞了,但是时间超过3秒后,没等顾客张三调用countDown方法就继续执行开始上菜了。
欢迎关注微信公众号:万猫学社,每周一分享Java技术干货。
面试官:CountDownLatch的实现原理是什么?
我:CountDownLatch有一个内部类叫做Sync,它继承了AbstractQueuedSynchronizer类,其中维护了一个整数state,并且保证了修改state的可见性和原子性。
创建CountDownLatch实例时,也会创建一个Sync的实例,同时把计数器的值传给Sync实例,具体是这样的:
public CountDownLatch(int count) {
    if (count < 0) throw new IllegalArgumentException("count < 0");
    this.sync = new Sync(count);
}
在 countDown方法中,只调用了Sync实例的releaseShared方法,具体是这样的:
public void countDown() {
    sync.releaseShared(1);
}
其中的releaseShared方法,先对计数器进行减1操作,如果减1后的计数器为0,唤醒被await方法阻塞的所有线程,具体是这样的:
public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) { //对计数器进行减一操作
        doReleaseShared();//如果计数器为0,唤醒被await方法阻塞的所有线程
        return true;
    }
    return false;
}
其中的tryReleaseShared方法,先获取当前计数器的值,如果计数器为0时,就直接返回;如果不为0时,使用CAS方法对计数器进行减1操作,具体是这样的:
protected boolean tryReleaseShared(int releases) {
    for (;;) {//死循环,如果CAS操作失败就会不断继续尝试。
        int c = getState();//获取当前计数器的值。
        if (c == 0)// 计数器为0时,就直接返回。
            return false;
        int nextc = c-1;
        if (compareAndSetState(c, nextc))// 使用CAS方法对计数器进行减1操作
            return nextc == 0;//如果操作成功,返回计数器是否为0
    }
}
在await方法中,只调用了Sync实例的acquireSharedInterruptibly方法,具体是这样的:
public void await() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}
其中acquireSharedInterruptibly方法,判断计数器是否为0,如果不为0则阻塞当前线程,具体是这样的:
public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    if (tryAcquireShared(arg) < 0)//判断计数器是否为0
        doAcquireSharedInterruptibly(arg);//如果不为0则阻塞当前线程
}
其中tryAcquireShared方法,是AbstractQueuedSynchronizer中的一个模板方法,其具体实现在Sync类中,其主要是判断计数器是否为零,如果为零则返回1,如果不为零则返回-1,具体是这样的:
protected int tryAcquireShared(int acquires) {
    return (getState() == 0) ? 1 : -1;
}
面试官:嗯,很不错,马上给你发offer。
微信公众号:万猫学社
微信扫描二维码
获得更多Java技术干货

腾讯面试居然跟我扯了半小时的CountDownLatch的更多相关文章
- 阿里面试居然跟我扯了半小时的CyclicBarrier
		一个大腹便便,穿着格子衬衫的中年男子,拿着一个贴满Logo的Mac向我走来,看着稀少的头发,我心想着肯定是顶级技术大牛吧!但是我也是一个才华横溢的人,稳住我们能赢. 面试官:您好,先做一下自我介绍吧! ... 
- 头条面试居然跟我扯了半小时的Semaphore
		一个长头发.穿着清爽的小姐姐,拿着一个崭新的Mac笔记本向我走来,看着来势汹汹,我心想着肯定是技术大佬吧!但是我也是一个才华横溢的人,稳住我们能赢. 面试官:看你简历上有写熟悉并发编程,Semapho ... 
- 腾讯面试:一条SQL语句执行得很慢的原因有哪些?---不看后悔系列
		说实话,这个问题可以涉及到 MySQL 的很多核心知识,可以扯出一大堆,就像要考你计算机网络的知识时,问你"输入URL回车之后,究竟发生了什么"一样,看看你能说出多少了. 之前腾讯 ... 
- 一个HashMap能跟面试官扯上半个小时
		一个HashMap能跟面试官扯上半个小时 <安琪拉与面试官二三事>系列文章 一个HashMap能跟面试官扯上半个小时 一个synchronized跟面试官扯了半个小时 一个volatile ... 
- 跟面试官侃半小时MySQL事务隔离性,从基本概念深入到实现
		提到MySQL的事务,我相信对MySQL有了解的同学都能聊上几句,无论是面试求职,还是日常开发,MySQL的事务都跟我们息息相关. 而事务的ACID(即原子性Atomicity.一致性Consiste ... 
- 跟面试官侃半小时MySQL事务,说完原子性、一致性、持久性的实现
		提到MySQL的事务,我相信对MySQL有了解的同学都能聊上几句,无论是面试求职,还是日常开发,MySQL的事务都跟我们息息相关. 而事务的ACID(即原子性Atomicity.一致性Consiste ... 
- 腾讯面试Android高级岗,居然被一个多线程基础面倒了?
		前言 一个在深圳从事开发五年的老友一个月前从原公司辞职后,昨天去腾讯总部面试Android高级岗,一面的时候,自我介绍后,陆陆续续问了很多问题,有着五年的从业经验很多项目开发的技术问题都回答的很通顺, ... 
- 一个static和面试官扯了一个小时,舌战加强版
		一:背景 1. 讲故事 最近也是奇怪,在社区里看到好几篇文章聊static 的玩法以及怎么拿这个和面试官扯半个小时,有点意思,点进去看都是java版的,这就没意思了,怎么也得有一篇和面试官扯C# 中的 ... 
- 腾讯面试官问我Java中boolean类型占用多少个字节?我说一个,面试官让我回家等通知
		本文首发于微信公众号:程序员乔戈里 什么是boolean类型,根据官方文档的描述: boolean: The boolean data type has only two possible value ... 
随机推荐
- Laravel路由不生效,除了首页全部404解决方案Nginx环境
			原因: 请求根目录/ (http://www.xxx.com/public/),会请求public/index.php 输入其他路由地址时,会把你的请求定位到:http://www.xxx.com/i ... 
- react-devtools安装调试
			初学react,Chrome F12调试,需要一款插件react-devtools. 网上大多对于翻墙不利索的同学大多才用了git源码.npm本地手动打包Chrome拓展程序.如:https://ww ... 
- Pytorch实现的语义分割器
			使用Detectron预训练权重输出 *e2e_mask_rcnn-R-101-FPN_2x* 的示例 从Detectron输出的相关示例 使用Detectron预训练权重输出 *e2e_keypoi ... 
- 0day堆(2)堆的调试实验
			堆的调试实验 调试态堆管理策略和常态堆管理策略:前者只使用空表不用块表,不真实 使用调试器加载函数会触发前者 __asm int3 调试最真实的栈 未启用块表的堆区信息 堆区起始位置(假设为0x005 ... 
- 第八次-非确定的自动机NFA确定化为DFA
			 提交作业 NFA 确定化为 DFA 子集法: f(q,a)={q1,q2,…,qn},状态集的子集 将{q1,q2,…,qn}看做一个状态A,去记录NFA读入输入符号之后可能达到的所有状态的集合. ... 
- 牛客网练习赛61 A+B
			A.打怪 思路:先判定当小怪的攻击力为0时,你能杀无数只怪,因为小怪A不动你,然后再计算每个小怪最多能给你造成多少伤害(用小怪的血量除以你的攻击力,也就是你砍它几下它会死,你先手,所以小怪肯定比你少砍 ... 
- 讲讲python中函数的参数
			python中函数的参数 形参:定义函数时代表函数的形式参数 实参:调用函数时传入的实际参数 列如: def f(x,y): # x,y形参 print(x, y) f(1, 2) # 1, 2 实参 ... 
- nginx与keepalived实现高可用+Apache实现负载均衡
			nginx与keepalived实现高可用 本实验使用了四台虚拟机 两台需要安装nginx及keepalived 两台安装Apache nginx可以源码安装也可以用yum安装nginx yum安装n ... 
- java 之 abstract、interface
			abstract (抽象) 用abstract关键字来修饰一个类时,这个类叫做抽象类: 用abstract来修饰一个方法时,该方法叫做抽象方法. 抽象方法:只有方法的声明,没有方法的实现.以分号结束: ... 
- LightOJ 1287 Where to Run(期望)
			题目链接:http://www.lightoj.com/volume_showproblem.php?problem=1287 题意:给定一个n个点的无向图(0到n-1),你开始在0.你开始遍历这个图 ... 
