面试官:说说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 ...
随机推荐
- python的运算符及数据类型的转换
python 目录 python 1.算术运算符 2.比较运算符 3.赋值运算符和复合赋值运算符 4.位运算符 5.逻辑运算符 6.成员运算符 7.身份运算符 8.常见的数据类型转换 1.算术运算符 ...
- redhat中的RHCS双机配置
1. 主机概述 主机名 主机IP 备注 node1 192.168.1.101 模拟fence设备 node2 192.168.1.102 rhcs双机节点 node3 192.168.1.103 r ...
- 设计模式 | Catalog设计模式,抵御业务方需求变动
大家好,这是一个全新的专题--设计模式. 其实可以选择的专题还有好几个,为什么选择设计模式呢?原因也很简单,首先是设计模式简单.易学.干货的文章固然好,但是普适性往往不强.另外一个很重要的点就是设计模 ...
- Unity 自己使用顶点描绘圆形UI图片
2020-09-10 在游戏的UI中,圆形图片的需求是很高的,但是,在Unity中想要实现圆形UI,一般的做法是是使用圆形Mask(遮罩),但是使用Mask的缺点很明显,主要有三点: 1.比较麻烦,使 ...
- Git【常见知识点速查】
文章更新时间:2020/06/17 一.基础知识点解析 Git工作流程 以上包括一些简单而常用的命令,但是先不关心这些,先来了解下面这4个专有名词. Workspace:工作区 Index / Sta ...
- kafka学习(五)Spring Boot 整合 Kafka
文章更新时间:2020/06/08 一.创建Spring boot 工程 创建过程不再描述,创建后的工程结构如下: POM文件中要加入几个依赖: <?xml version="1.0& ...
- UOS服务器常见问题
UOS服务器安装完成之后,xshell工具无法远程, 描述 我安装完系统后,用xshell工具无法远程,必须在系统上才能进去我是通过centos7的kvm虚拟工具上刷的统信的系统,安装方式: 最小化安 ...
- python3-day4
一.列表生成式,迭代器和生成器 1)列表生成式 把列表 [0,1,2,3,4,5,6,7,8,9]里的每个值添加1 1 >>>a = [0,1,2,3,4,5,6,7,8,9] 2 ...
- (转载)IO模型
本文转载自网络. 如有侵权,请联系处理! 简介 参考<UNIX Network Programming Volume 1, ThirdEdition [Electronic resources] ...
- Python练习题 042:Project Euler 014:最长的考拉兹序列
本题来自 Project Euler 第14题:https://projecteuler.net/problem=14 ''' Project Euler: Problem 14: Longest C ...