当前位置:网站首页>多个线程之间如何协同
多个线程之间如何协同
2022-07-07 17:44:00 【码农小胖哥】
1. CountDownLatch 计数器
在多线程协作完成任务的时候,有时候需要等待其他线程完成任务后,主线程才能继续执行,我们可以使用 Thread
类的 join()
方法,让主线程等待被 join 的线程执行完毕主线程再执行。当然也可以通过线程的消息通信实现,但是今天我们 的并发包里面有一个工具,十分方便的完成这个任务。
举个通俗的例子,二狗子带兵打仗,一共 6 个小兵仔,没有二狗子将军的号令,是不敢贸然出击,出击就变炮灰了。所以二狗子一声令下,所有的小兵仔就发了疯的嘴巴喊着冲鸭,去杀敌。
小兵仔就像 6 个线程,二狗子就像是主线程,当线程调用 CountDownLatch.countDown()
方法时就会对计数器的值 -1,直到计数器的值为 0 的时候, 调用 await 方法的线程 才能继续往下执行。
CountDownLatch
的重要方法
public CountDownLatch(int count)
构造方法传入一个 整形数字 N,之后调用 countDown()
方法会对 N 减 1,直到 N = 0 ,当前调用 await
方法的线程继续执行,否则会被阻塞。
CountDownLatch 的方法不是很多,将它们一个个列举出 :
await() throws InterruptedException:调用该方法的线程等到构造方法传入的 N 减到 0 的时候,才能继续往下执行;
await(long timeout, TimeUnit unit):与上面的 await 方法功能一致,只不过这里有了时间限制,调用该方法的线程等到指定的 timeout 时间后,不管 N 是否减至为 0,都会继续往下执行;
countDown():使 CountDownLatch 初始值 N 减 1;
long getCount():获取当前 CountDownLatch 维护的值;
1.1 听我号令攻城
二狗子将军带兵攻城,定义我们的 小兵仔
publicclass Soldier implements Runnable {
privatefinal CountDownLatch doneSignal;
public Soldier(CountDownLatch doneSignal) {
this.doneSignal = doneSignal;
}
@Override
public void run() {
try {
System.out.println("name = " + Thread.currentThread().getName() + "上阵杀敌,冲鸭");
} finally {
// 对计数器 - 1
doneSignal.countDown();
}
}
}
接着模拟我们的二狗子发号施令
publicclass Commander {
public static void main(String[] args) throws InterruptedException {
int n = 6;
// 初始化计数器为 6
CountDownLatch doneSignal = new CountDownLatch(n);
ExecutorService e = Executors.newFixedThreadPool(n);
//模拟 6 个线程
for (int i = 0; i < n; ++i) {
e.execute(new Soldier("doneSignal" + i, doneSignal));
}
System.out.println("所有士兵就位,听我号令");
// 当 doneSignal 每次执行 countDown - 1 操作,变成了 0 之后所有线程唤醒执行
doneSignal.await();
System.out.println("攻城成功,回去邀功领赏吃羊蝎子补一补");
e.shutdown();
}
}
1.2 跑步比赛,计时开始
运动员进行跑步比赛时,假设有 6 个运动员参与比赛,裁判员在终点会为这 6 个运动员分别计时,可以想象没当一个运动员到达终点的时候,对于裁判员来说就少了一个计时任务。
直到所有运动员都到达终点了,裁判员的任务也才完成。这 6 个运动员可以类比成 6 个线程,当线程调用 CountDownLatch.countDown 方法时就会对计数器的值减一,直到计数器的值为 0 的时候,裁判员(调用 await 方法的线程)才能继续往下执行。
publicclass Running {
public static void main(String[] args) throws InterruptedException {
// 裁判开始信号
CountDownLatch startSignal = new CountDownLatch(1);
int number = 4;
// 运动员跑步完成信号
CountDownLatch doneSignal = new CountDownLatch(number);
ExecutorService executorService = Executors.newFixedThreadPool(number);
for (int i = 0; i < number; i++) {
finalint currnt = i;
executorService.execute(() -> {
try {
// 让所有的运动员等待阻塞在这里,直到信号发出
startSignal.await();
System.out.println(Thread.currentThread().getName() + "迈开步子使劲跑");
TimeUnit.SECONDS.sleep(currnt);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 跑完后
doneSignal.countDown();
System.out.println(Thread.currentThread().getName() + "到达终点");
}
});
}
System.out.println("裁判员发号施令啦!!!");
startSignal.countDown();
//等待所有的运动员跑完
doneSignal.await();
System.out.println("所有运动员到达终点,比赛结束!");
executorService.shutdown();
}
}
2. 循环栅栏:CyclicBarrier
和 CountDownLatch
一样具有等待计数的功能,但是相比于 CountDownLatch
功能更加强大。
还是一个通俗的例子,马拉松运动,很多运动员,但是跑道有限每次只允许 6 个运动员开跑,每次等到 6 个就开跑,然后下一队,一直循环....
在比赛开始时,就需要 6 个运动员在比赛开始的时候都站在起点了,裁判员吹哨后才能开始跑步。跑道起点就相当于“barrier”,是临界点,而这 6 个运动员就类比成线程的话,就是这 6 个线程都必须到达指定点了,意味着凑齐了一波,然后才能继续执行,否则每个线程都得阻塞等待,直至凑齐一波即可。cyclic 是循环的意思,也就是说 CyclicBarrier 当多个线程凑齐了一波之后,仍然有效,可以继续凑齐下一波。
//等到所有的线程都到达指定的临界点
await() throws InterruptedException, BrokenBarrierException
//与上面的await方法功能基本一致,只不过这里有超时限制,阻塞等待直至到达超时时间为止
await(long timeout, TimeUnit unit) throws InterruptedException,
BrokenBarrierException, TimeoutException
//获取当前有多少个线程阻塞等待在临界点上
int getNumberWaiting()
//用于查询阻塞等待的线程是否被中断
boolean isBroken()
//将屏障重置为初始状态。如果当前有线程正在临界点等待的话,将抛出BrokenBarrierException。
void reset()
另外需要注意的是,CyclicBarrier 提供了这样的构造方法:
public CyclicBarrier(int parties, Runnable barrierAction)
可以用来,当指定的线程都到达了指定的临界点的时,接下来执行的操作可以由 barrierAction 传入即可。一个回调方式。
马拉松代码示例
publicclass CyclicBarrierDemo {
private CyclicBarrier cyclicBarrier;
private ExecutorService executorService;
public CyclicBarrierDemo(CyclicBarrier cyclicBarrier) {
this.cyclicBarrier = cyclicBarrier;
executorService = Executors.newFixedThreadPool(cyclicBarrier.getParties());
}
public void startRun() {
for (int i = 0; i < cyclicBarrier.getParties() * 3; i++) {
int current = i;
executorService.execute(() -> {
try {
System.out.println(Thread.currentThread().getName() + "运动员,准备就绪");
// 每个运动员执行到这就会 对 N - 1,变为 0 则放一波线程运行,然后重置 N
cyclicBarrier.await();
System.out.println(Thread.currentThread().getName() + "运动员,开跑");
TimeUnit.SECONDS.sleep(current);
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
});
}
executorService.shutdown();
}
}
单元测试
publicclass CyclicBarrierDemoTest {
@Test
public void testRun() throws InterruptedException {
CyclicBarrier cyclicBarrier = new CyclicBarrier(4, () -> System.out.println("所有运动员准备就绪,裁判发令..."));
CyclicBarrierDemo cyclicBarrierDemo = new CyclicBarrierDemo(cyclicBarrier);
cyclicBarrierDemo.startRun();
Thread.currentThread().join();
}
}
3. CountDownLatch 与 CyclicBarrier 的比较
CountDownLatch 与 CyclicBarrier 都是用于控制并发的工具类,都可以理解成维护的就是一个计数器,但是这两者还是各有不同侧重点的:
CountDownLatch 一般用于某个线程 A 等待若干个其他线程执行完任务之后,它才执行;而 CyclicBarrier 一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行;CountDownLatch 强调一个线程等多个线程完成某件事情。CyclicBarrier 是多个线程互等,等大家都完成,再携手共进。
调用 CountDownLatch 的 countDown 方法后,当前线程并不会阻塞,会继续往下执行;而调用 CyclicBarrier 的 await 方法,会阻塞当前线程,直到 CyclicBarrier 指定的线程全部都到达了指定点的时候,才能继续往下执行;
CountDownLatch 方法比较少,操作比较简单,而 CyclicBarrier 提供的方法更多,比如能够通过 getNumberWaiting(),isBroken()这些方法获取当前多个线程的状态,并且 CyclicBarrier 的构造方法可以传入 barrierAction,指定当所有线程都到达时执行的业务功能;
CountDownLatch 是不能复用的,而 CyclicLatch 是可以复用的。
4.总结
CountDownLatch 和 CyclicBarrier 是 Java 并发包提供的两个非常易用的线程同步工具类,这两个工具类用法的区别在这里还是有必要再强调一下:
CountDownLatch 主要用来解决一个线程等待多个线程的场景,可以类比旅游团团长要等待所有的游客到齐才能去下一个景点;
而CyclicBarrier 是一组线程之间互相等待,更像是几个驴友之间不离不弃。除此之外 CountDownLatch 的计数器是不能循环利用的,也就是说一旦计数器减到 0,再有线程调用 await(),该线程会直接通过。
但CyclicBarrier 的计数器是可以循环利用的,而且具备自动重置的功能,一旦计数器减到 0 会自动重置到你设置的初始值。除此之外,CyclicBarrier 还可以设置回调函数,可以说是功能丰富。
边栏推荐
- [confluence] JVM memory adjustment
- 2022.07.02
- LeetCode 535(C#)
- Kirin Xin'an with heterogeneous integration cloud financial information and innovation solutions appeared at the 15th Hunan Financial Technology Exchange Conference
- 杰理之测试盒配置声道【篇】
- 【剑指offer】剑指 Offer II 012. 左右两边子数组的和相等
- String - string (Lua)
- RESTAPI 版本控制策略【eolink 翻译】
- ASP.NET体育馆综合会员管理系统源码,免费分享
- 网易云信参与中国信通院《实时音视频服务(RTC)基础能力要求及评估方法》标准编制...
猜你喜欢
Automatic classification of defective photovoltaic module cells in electroluminescence images-论文阅读笔记
9 原子操作类之18罗汉增强
Experiment 1 of Compilation Principle: automatic implementation of lexical analyzer (Lex lexical analysis)
关于ssh登录时卡顿30s左右的问题调试处理
Welcome to the markdown editor
PMP对工作有益吗?怎么选择靠谱平台让备考更省心省力!!!
项目经理『面试八问』,看了等于会了
使用高斯Redis实现二级索引
Kirin Xin'an with heterogeneous integration cloud financial information and innovation solutions appeared at the 15th Hunan Financial Technology Exchange Conference
微信公众号OAuth2.0授权登录并显示用户信息
随机推荐
648. 单词替换
Browse the purpose of point setting
Numpy——2. Shape of array
R language ggplot2 visualization: use the ggstripchart function of ggpubr package to visualize the dot strip plot, set the position parameter, and configure the separation degree of different grouped
The DBSCAN function of FPC package of R language performs density clustering analysis on data, checks the clustering labels of all samples, and the table function calculates the two-dimensional contin
2022.07.05
小试牛刀之NunJucks模板引擎
how to prove compiler‘s correctness
tp6 实现佣金排行榜
R language dplyr package mutate_ At function and min_ The rank function calculates the sorting sequence number value and ranking value of the specified data column in the dataframe, and assigns the ra
Automatic classification of defective photovoltaic module cells in electroluminescence images-論文閱讀筆記
2022如何评估与选择低代码开发平台?
怎么在手机上买股票开户 股票开户安全吗
索引总结(突击版本)
“本真”是什么意思
模拟实现string类
# 欢迎使用Markdown编辑器
How to buy bank financial products? Do you need a bank card?
Nunjuks template engine
how to prove compiler‘s correctness