面试官:说说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 ...
随机推荐
- C#开发PACS医学影像三维重建(一):使用VTK重建3D影像
VTK简介: VTK是一个开源的免费软件系统,主要用于三维计算机图形学.图像处理和可视化.Vtk是在面向对象原理的基础上设计和实现的,它的内核是用C++构建的. 因为使用C#语言开发,而VTK是C++ ...
- PG-跨库操作-postgres_fdw
接上一篇<PG-跨库操作-dblink>:讲下postgres_fdw的使用:postgres_fdw工作原理详细介绍可以去看下<PostgreSQL指南>第4章: 对FDW特 ...
- spring cloud微服务快速教程之(十四)spring cloud feign使用okhttp3--以及feign调用参数丢失的说明
0-前言 spring cloud feign 默认使用httpclient,需要okhttp3的可以进行切换 当然,其实两者性能目前差别不大,差别较大的是很早之前的版本,所以,喜欢哪个自己选择: 1 ...
- 论文阅读 SNAPSHOT ENSEMBLES
引入 1. 随机梯度下降的特点 随机梯度下降法(Stochastic Gradient Descent)作为深度学习中主流使用的最优化方法, 有以下的优点: 躲避和逃离假的鞍点和局部极小点的能力 这篇 ...
- Spark Extracting,transforming,selecting features
Spark(3) - Extracting, transforming, selecting features 官方文档链接:https://spark.apache.org/docs/2.2.0/m ...
- chrome浏览器的两个坑,以及其他
chrome打开本地网页时,不能保存cookiechrome拒绝使用ajax访问本地文件(火狐可以) ipinfo.io/ip 获得公网iphttps://v1.hitokoto.cn/ 获得一句动漫 ...
- [VBA原创源代码] excelhome 汇总多工作表花名册
生病了,一点一滴的积累,慢慢康复,今年十月,我就 2 周岁了. 以下代码完成了excelhome中留的作业 http://club.excelhome.net/forum.php?mod=viewth ...
- visio调整画布大小的简便方法
按住Ctrl,然后鼠标在边缘拉拽即可.
- Git 看这一篇就够了
上一篇讲 Git 的文章发出来没想到效果特别好,很多读者都要求继续深入的写. 那今天齐姐简单讲下 Git 的实现原理,知其所以然才能知其然:并且梳理了日常最常用的 12 个命令,分为三大类分享给你. ...
- 多测师_肖sir_性能测试之性能测试了解001(jmeter)
一.了解jmeter 1.Jmeter的概念? JMeter是Apache组织开发的基于Java的压力测试工具.具有开源免费.框架灵活.多平台支持等优势.除了压力测试外,JMeter在接口测试方面也有 ...