面试官:说说CountDownLatch,CyclicBarrier,Semaphore的原理?
CountDownLatch
CountDownLatch适用于在多线程的场景需要等待所有子线程全部执行完毕之后再做操作的场景。
举个例子,早上部门开会,有人在上厕所,这时候需要等待所有人从厕所回来之后才能开始会议。
public class CountDownLatchTest {
private static int num = 3;
private static CountDownLatch countDownLatch = new CountDownLatch(num);
private static ExecutorService executorService = Executors.newFixedThreadPool(num);
public static void main(String[] args) throws Exception{
executorService.submit(() -> {
System.out.println("A在上厕所");
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
countDownLatch.countDown();
System.out.println("A上完了");
}
});
executorService.submit(()->{
System.out.println("B在上厕所");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
countDownLatch.countDown();
System.out.println("B上完了");
}
});
executorService.submit(()->{
System.out.println("C在上厕所");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
countDownLatch.countDown();
System.out.println("C上完了");
}
});
System.out.println("等待所有人从厕所回来开会...");
countDownLatch.await();
System.out.println("所有人都好了,开始开会...");
executorService.shutdown();
}
}
代码执行结果:
A在上厕所
B在上厕所
等待所有人从厕所回来开会...
C在上厕所
B上完了
C上完了
A上完了
所有人都好了,开始开会...
初始化一个CountDownLatch实例传参3,因为我们有3个子线程,每次子线程执行完毕之后调用countDown()方法给计数器-1,主线程调用await()方法后会被阻塞,直到最后计数器变为0,await()方法返回,执行完毕。他和join()方法的区别就是join会阻塞子线程直到运行结束,而CountDownLatch可以在任何时候让await()返回,而且用ExecutorService没法用join了,相比起来,CountDownLatch更灵活。
CountDownLatch基于AQS实现,volatile变量state维持倒数状态,多线程共享变量可见。
- CountDownLatch通过构造函数初始化传入参数实际为AQS的state变量赋值,维持计数器倒数状态
- 当主线程调用await()方法时,当前线程会被阻塞,当state不为0时进入AQS阻塞队列等待。
- 其他线程调用countDown()时,state值原子性递减,当state值为0的时候,唤醒所有调用await()方法阻塞的线程
CyclicBarrier
CyclicBarrier叫做回环屏障,它的作用是让一组线程全部达到一个状态之后再全部同时执行,而且他有一个特点就是所有线程执行完毕之后是可以重用的。
public class CyclicBarrierTest {
private static int num = 3;
private static CyclicBarrier cyclicBarrier = new CyclicBarrier(num, () -> {
System.out.println("所有人都好了,开始开会...");
System.out.println("-------------------");
});
private static ExecutorService executorService = Executors.newFixedThreadPool(num);
public static void main(String[] args) throws Exception{
executorService.submit(() -> {
System.out.println("A在上厕所");
try {
Thread.sleep(4000);
System.out.println("A上完了");
cyclicBarrier.await();
System.out.println("会议结束,A退出");
} catch (Exception e) {
e.printStackTrace();
}finally {
}
});
executorService.submit(()->{
System.out.println("B在上厕所");
try {
Thread.sleep(2000);
System.out.println("B上完了");
cyclicBarrier.await();
System.out.println("会议结束,B退出");
} catch (Exception e) {
e.printStackTrace();
}finally {
}
});
executorService.submit(()->{
System.out.println("C在上厕所");
try {
Thread.sleep(3000);
System.out.println("C上完了");
cyclicBarrier.await();
System.out.println("会议结束,C退出");
} catch (Exception e) {
e.printStackTrace();
}finally {
}
});
executorService.shutdown();
}
}
输出结果为:
A在上厕所
B在上厕所
C在上厕所
B上完了
C上完了
A上完了
所有人都好了,开始开会...
-------------------
会议结束,A退出
会议结束,B退出
会议结束,C退出
从结果来看和CountDownLatch非常相似,初始化传入3个线程和一个任务,线程调用await()之后进入阻塞,计数器-1,当计数器为0时,就去执行CyclicBarrier中构造函数的任务,当任务执行完毕后,唤醒所有阻塞中的线程。这验证了CyclicBarrier让一组线程全部达到一个状态之后再全部同时执行的效果。
再举个例子来验证CyclicBarrier可重用的效果。
public class CyclicBarrierTest2 {
private static int num = 3;
private static CyclicBarrier cyclicBarrier = new CyclicBarrier(num, () -> {
System.out.println("-------------------");
});
private static ExecutorService executorService = Executors.newFixedThreadPool(num);
public static void main(String[] args) throws Exception {
executorService.submit(() -> {
System.out.println("A在上厕所");
try {
Thread.sleep(4000);
System.out.println("A上完了");
cyclicBarrier.await();
System.out.println("会议结束,A退出,开始撸代码");
cyclicBarrier.await();
System.out.println("C工作结束,下班回家");
cyclicBarrier.await();
} catch (Exception e) {
e.printStackTrace();
} finally {
}
});
executorService.submit(() -> {
System.out.println("B在上厕所");
try {
Thread.sleep(2000);
System.out.println("B上完了");
cyclicBarrier.await();
System.out.println("会议结束,B退出,开始摸鱼");
cyclicBarrier.await();
System.out.println("B摸鱼结束,下班回家");
cyclicBarrier.await();
} catch (Exception e) {
e.printStackTrace();
} finally {
}
});
executorService.submit(() -> {
System.out.println("C在上厕所");
try {
Thread.sleep(3000);
System.out.println("C上完了");
cyclicBarrier.await();
System.out.println("会议结束,C退出,开始摸鱼");
cyclicBarrier.await();
System.out.println("C摸鱼结束,下班回家");
cyclicBarrier.await();
} catch (Exception e) {
e.printStackTrace();
} finally {
}
});
executorService.shutdown();
}
}
输出结果:
A在上厕所
B在上厕所
C在上厕所
B上完了
C上完了
A上完了
-------------------
会议结束,A退出,开始撸代码
会议结束,B退出,开始摸鱼
会议结束,C退出,开始摸鱼
-------------------
C摸鱼结束,下班回家
C工作结束,下班回家
B摸鱼结束,下班回家
-------------------
从结果来看,每个子线程调用await()计数器减为0之后才开始继续一起往下执行,会议结束之后一起进入摸鱼状态,最后一天结束一起下班,这就是可重用。
CyclicBarrier还是基于AQS实现的,内部维护parties记录总线程数,count用于计数,最开始count=parties,调用await()之后count原子递减,当count为0之后,再次将parties赋值给count,这就是复用的原理。
- 当子线程调用await()方法时,获取独占锁,同时对count递减,进入阻塞队列,然后释放锁
- 当第一个线程被阻塞同时释放锁之后,其他子线程竞争获取锁,操作同1
- 直到最后count为0,执行CyclicBarrier构造函数中的任务,执行完毕之后子线程继续向下执行
Semaphore
Semaphore叫做信号量,和前面两个不同的是,他的计数器是递增的。
public class SemaphoreTest {
private static int num = 3;
private static int initNum = 0;
private static Semaphore semaphore = new Semaphore(initNum);
private static ExecutorService executorService = Executors.newFixedThreadPool(num);
public static void main(String[] args) throws Exception{
executorService.submit(() -> {
System.out.println("A在上厕所");
try {
Thread.sleep(4000);
semaphore.release();
System.out.println("A上完了");
} catch (Exception e) {
e.printStackTrace();
}finally {
}
});
executorService.submit(()->{
System.out.println("B在上厕所");
try {
Thread.sleep(2000);
semaphore.release();
System.out.println("B上完了");
} catch (Exception e) {
e.printStackTrace();
}finally {
}
});
executorService.submit(()->{
System.out.println("C在上厕所");
try {
Thread.sleep(3000);
semaphore.release();
System.out.println("C上完了");
} catch (Exception e) {
e.printStackTrace();
}finally {
}
});
System.out.println("等待所有人从厕所回来开会...");
semaphore.acquire(num);
System.out.println("所有人都好了,开始开会...");
executorService.shutdown();
}
}
输出结果为:
A在上厕所
B在上厕所
等待所有人从厕所回来开会...
C在上厕所
B上完了
C上完了
A上完了
所有人都好了,开始开会...
稍微和前两个有点区别,构造函数传入的初始值为0,当子线程调用release()方法时,计数器递增,主线程acquire()传参为3则说明主线程一直阻塞,直到计数器为3才会返回。
Semaphore还还还是基于AQS实现的,同时获取信号量有公平和非公平两种策略
- 主线程调用acquire()方法时,用当前信号量值-需要获取的值,如果小于0,则进入同步阻塞队列,大于0则通过CAS设置当前信号量为剩余值,同时返回剩余值
- 子线程调用release()给当前信号量值计数器+1(增加的值数量由传参决定),同时不停的尝试因为调用acquire()进入阻塞的线程
总结
CountDownLatch通过计数器提供了比join更灵活的多线程控制方式,CyclicBarrier也可以达到CountDownLatch的效果,而且有可复用的特点,Semaphore则是采用信号量递增的方式,开始的时候并不需要关注需要同步的线程个数,并且提供获取信号的公平和非公平策略。
- END -
面试官:说说CountDownLatch,CyclicBarrier,Semaphore的原理?的更多相关文章
- 【对线面试官】CountDownLatch和CyclicBarrier的区别
<对线面试官>系列目前已经连载31篇啦,这是一个讲人话面试系列 [对线面试官]Java注解 [对线面试官]Java泛型 [对线面试官] Java NIO [对线面试官]Java反射 &am ...
- 并发包下常见的同步工具类详解(CountDownLatch,CyclicBarrier,Semaphore)
目录 1. 前言 2. 闭锁CountDownLatch 2.1 CountDownLatch功能简介 2.2 使用CountDownLatch 2.3 CountDownLatch原理浅析 3.循环 ...
- CountDownLatch/CyclicBarrier/Semaphore 使用过吗?
CountDownLatch/CyclicBarrier/Semaphore 使用过吗?下面详细介绍用法: 一,(等待多线程完成的)CountDownLatch 背景; countDownLatch ...
- Java并发编程工具类 CountDownLatch CyclicBarrier Semaphore使用Demo
Java并发编程工具类 CountDownLatch CyclicBarrier Semaphore使用Demo CountDownLatch countDownLatch这个类使一个线程等待其他线程 ...
- 并发包下常见的同步工具类(CountDownLatch,CyclicBarrier,Semaphore)
在实际开发中,碰上CPU密集且执行时间非常耗时的任务,通常我们会选择将该任务进行分割,以多线程方式同时执行若干个子任务,等这些子任务都执行完后再将所得的结果进行合并.这正是著名的map-reduce思 ...
- 面试官再问你 HashMap 底层原理,就把这篇文章甩给他看
前言 HashMap 源码和底层原理在现在面试中是必问的.因此,我们非常有必要搞清楚它的底层实现和思想,才能在面试中对答如流,跟面试官大战三百回合.文章较长,介绍了很多原理性的问题,希望对你有所帮助~ ...
- Java中的4个并发工具类 CountDownLatch CyclicBarrier Semaphore Exchanger
在 java.util.concurrent 包中提供了 4 个有用的并发工具类 CountDownLatch 允许一个或多个线程等待其他线程完成操作,课题点 Thread 类的 join() 方法 ...
- 高并发第十单:J.U.C AQS(AbstractQueuedSynchronizer) 组件:CountDownLatch. CyclicBarrier .Semaphore
这里有一篇介绍AQS的文章 非常好: Java并发之AQS详解 AQS全名:AbstractQueuedSynchronizer,是并发容器J.U.C(java.lang.concurrent)下lo ...
- CountDownLatch CyclicBarrier Semaphore 比较
document CountDownLatch A synchronization aid that allows one or more threads to wait until a set of ...
- 多线程中 CountDownLatch CyclicBarrier Semaphore的使用
CountDownLatch 调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行.也可以传入时间,表示时间到之后,count还没有为0的时候,就会继续执行. package ...
随机推荐
- Linux实战(10):ssh密码被拒绝
网上的操作都试过了,可还是不行,最终通过重装ssh服务解决. 参考资料:https://blog.csdn.net/VicdorLin/article/details/90727826 https:/ ...
- Redis 发布订阅,小功能大用处,真没那么废材!
今天小黑哥来跟大家介绍一下 Redis 发布/订阅功能. 也许有的小伙伴对这个功能比较陌生,不太清楚这个功能是干什么的,没关系小黑哥先来举个例子. 假设我们有这么一个业务场景,在网站下单支付以后,需要 ...
- Spring学习(二)Spring IoC 和 DI 简介
一.IOC(控制反转) 定义:反转控制 (Inversion Of Control)的缩写,即创建对象的反转控制. 正向控制:若要使用某个对象,需要自己去负责对象的创建. 反向控制:若要使用某个对象, ...
- Java8——Stream流式操作的一点小总结
我发现,自从我学了Stream流式操作之后,工作中使用到的频率还是挺高的,因为stream配合着lambda表达式或者双冒号(::)使用真的是优雅到了极致!今天就简单分(搬)享(运)一下我对strea ...
- Linux服务器内存监控—每小时检查&超出发送邮件&重启占用最高的Java程式
简介与优点 使用该脚本能自行判断系统内存使用情况是否超出设定百分比 能在超出预警值时执行重启程式的操作 能记录重启过程,并将具体LOG邮件发送给指定收信人 可以设定Crontab排程,达成每隔一段时间 ...
- Python-__init__ 和 __new__区别和原理
__init__ 和 __new__区别 1. 从传递参数角度看,__init__第一个参数是实例本身, __new__传递的是类本身 2. 从执行顺序角度看,__new__方法执行在 __init_ ...
- notepad快捷使用
1.快捷键 参考:https://www.php.cn/tool/notepad/428638.html notepad++是经常使用的一款编辑器软件,在编辑特殊文本的时候(html,java...) ...
- 【题解】[LNOI2014]LCA
题目戳我 \(\text{Solution:}\) 这题的转化思想很巧妙-- 考虑把\(dep\)给拆掉. 首先可以明确的是,每一个\(LCA\)一定在\(root\to z\)的路径上. 考虑一个\ ...
- node-macaddress
下载 node-macaddressnode-macaddress 检索Linux.OS X和Windows中的MAC地址. 关于MAC地址的一个常见误解是,每个主机只有一个MAC地址, 虽然一个主机 ...
- K8S-kubeadm-集群证书续签
ETCD证书 自签证书颁发机构(CA) ca.crt ca.key etcd集群中相互通信事业的客户端证书 peer.crt peer.key pod中定义Liveness探针事业的客户端证书 hea ...