当前位置:网站首页>腾讯面试居然跟我扯了半小时的CountDownLatch
腾讯面试居然跟我扯了半小时的CountDownLatch
2022-07-30 05:15:00 【m0_67392931】
文章持续更新,微信搜索「 万猫学社 」第一时间阅读。
关注后回复「 电子书 」,免费获取12本Java必读技术书籍。
一个长头发、穿着清爽的小姐姐,拿着一个崭新的Mac笔记本向我走来,看着来势汹汹,我心想着肯定是技术大佬吧!但是我也是一个才华横溢的人,稳住我们能赢。
面试官:看你简历上有写熟悉并发编程,CountDownLatch一定用过吧,跟我说说它!
我:CountDownLatch是JDK提供的一个同步工具,它可以让一个或多个线程等待,一直等到其他线程中执行完成一组操作。
面试官:CountDownLatch有哪些常用的方法?
我:有countDown方法和await方法,CountDownLatch在初始化时,需要指定用给定一个整数作为计数器。当调用countDown方法时,计数器会被减1;当调用await方法时,如果计数器大于0时,线程会被阻塞,一直到计数器被countDown方法减到0时,线程才会继续执行。计数器是无法重置的,当计数器被减到0时,调用await方法都会直接返回。
面试官:调用
countDown方法时,线程也会阻塞嘛?
我:不会的,调用countDown的线程可以继续执行,不需要等待计数器被减到0,只是调用await方法的线程需要等待。
面试官:可以举一个使用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();
for (Thread thread : threads) {
thread.join();
}
}
}
运行以后的结果应该是这样的:
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方法才继续执行。
面试官:如果有一个顾客迟迟没到,饭店都打样了,也不能一直等啊,应该这么办?
我:可以使用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方法就继续执行开始上菜了。
面试官: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。
本故事纯属虚构,如有雷同实属巧合
文章持续更新,微信搜索「 万猫学社 」第一时间阅读。
关注后回复「 电子书 」,免费获取12本Java必读技术书籍。
边栏推荐
- JVM面试总结
- VisualStudio2022 local debugging entry is particularly slow problem solving
- Seata异常:endpoint format should like ip:port
- 关于组织开展2022年广东省技术先进型服务企业认定工作的通知
- go语言学习笔记二
- Hexagon_V65_Programmers_Reference_Manual(11)
- Participate in open source, let programmers regain their blood and passion
- 从驱动表和被驱动表来快速理解MySQL中的内连接和外连接
- MySQL索引常见面试题(2022版)
- Concurrent Programming Review
猜你喜欢

Golang go-redis cluster模式下不断创建新连接,效率下降问题解决

即刻报名|如何降低云上数据分析成本?

VisualStudio2022 local debugging entry is particularly slow problem solving

力扣1047-删除字符串中的所有相邻重复项——栈

ugly programmer

Hexagon_V65_Programmers_Reference_Manual (12)

黄金圈法则:成功者必备的深度思考方法

Go语学习笔记 - gorm使用 - 事务操作 Web框架Gin(十一)

Us to raise interest rates by 75 basis points in "technical recession"?Encryption market is recovering
![[Redis Master Cultivation Road] Jedis - the basic use of Jedis](/img/e3/0c6efd03432a01f857796f0bf648ef.png)
[Redis Master Cultivation Road] Jedis - the basic use of Jedis
随机推荐
Kyligence 出席华为全球智慧金融峰会,加速拓展全球市场
Redis基础学习
WeChat payment and payment callback
std::vector中保存指针时用法
“幻核”跑路,数字藏品路在何方?
美国再次加息75个基点 陷入“技术性衰退”?加密市场却呈现复苏力量
Seata异常:endpoint format should like ip:port
leetcode hot 100(刷题篇11)(231/235/237/238/292/557/240/36)offer/3/4/5
视野 | KeyDB:为 Web 应用而生的高性能 Redis 分支
[Redis Master Cultivation Road] Jedis - the basic use of Jedis
Golang channel implementation principle
GO language study notes one
Nuxt3 learning
mysql基础(4)
丑陋的程序员
一文带你吃透js处理树状结构数据的增删改查
Unity踩坑记录 —— GetComponent的使用
VisualStudio2022 local debugging entry is particularly slow problem solving
如何让 (a == 1 && a == 2 && a == 3) 的值为true?
ms project2010项目管理软件使用技巧总结