文章目录
其实
我自认为
CountDownLatch类是进入AQS世界一个非常好非常好的通道,因此写完这篇文章,我打算再写一篇关于CountDownLatch源码的文章。
1 原理简介
CountDownLatch原理可以用下图进行表示:
这里注意一下:
每个线程都可以调用countDown()1次,或多次 —> 并非每个线程都只能调用1次
其实这个CountDownLatch和Thread的join方法有点类似,下图是我在《【并发编程】— Thread类中的join方法》一文中画的join的原理图,有兴趣的可以对比一下。
2 具体使用方法
2.1 demo1 — await不传入时间,保证当前线程的其他操作在最后执行
- code
package com.nrsc.ch2.juctools;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.CountDownLatch;
@Slf4j
public class CountDownLatchDemo1 {
private final static int threadCount = 200;
public static void main(String[] args) throws InterruptedException {
final CountDownLatch countDownLatch = new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; i++) {
final int threadNum = i;
new Thread(() -> {
try {
test(threadNum);
} catch (Exception e) {
log.error("exception:", e);
} finally { //放在finally块里保证线程无论怎样都会执行countDown方法
countDownLatch.countDown();
}
}).start();
}
countDownLatch.await();
//可以想到finish肯定会在上面的线程都运行完才执行
log.info("finish");
}
private static void test(int i) throws InterruptedException {
Thread.sleep(100);
log.info("threadNum:{}", i);
Thread.sleep(100);
}
}
- 测试结果
2.2 demo2 — await传入时间t,当前线程等其他线程时间t后就运行其他操作
- code
package com.nrsc.ch2.juctools;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@Slf4j
public class CountDownLatchDemo2 {
private final static int threadCount = 200;
public static void main(String[] args) throws InterruptedException {
final CountDownLatch countDownLatch = new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; i++) {
final int threadNum = i;
new Thread(() -> {
try {
test(threadNum);
} catch (Exception e) {
log.error("exception:", e);
} finally {
countDownLatch.countDown();
}
}).start();
}
//等其他线程11MILLISECONDS后,放行当前线程await后的其他操作
countDownLatch.await(11, TimeUnit.MILLISECONDS);
log.info("finish");
}
private static void test(int i) throws InterruptedException {
Thread.sleep(10);
log.info("threadNum:{}", i);
}
}
- 测试结果
注意:
我上面的运行结果并不是唯一的,其实finish在最后输出也是有可能的。这里我让其他线程睡了10MILLISECONDS,而主线程会等11MILLISECONDS,其他线程是有可能都在11MILLISECONDS之内运行完的。
2.3 发令枪
相信很多人都听说过,CountDownLatch可以做类似发令枪的行为,我这里也实现了一个demo,代码如下:
要准确理解它为什么被比喻成发令枪,一定要多注意一下我代码中的注释,和文末测试结果图中的注释。
package com.nrsc.ch2.juctools;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.*;
@Slf4j
public class CountDownLatchDemo3 {
/***
* 继承了Callable的内部类
*/
private static class Runner implements Callable<Integer> {
private CountDownLatch judge;//裁判
private CountDownLatch runner;//跑步者
public Runner(CountDownLatch begin, CountDownLatch end) {
super();
this.judge = begin;
this.runner = end;
}
@Override
public Integer call() throws Exception {
int score = new Random().nextInt(10);
//------------------------------------------------------------
//judge不进行countDown,所有的线程都会在这里阻塞住
//------------------------------------------------------------
log.info("线程-{}准备就绪", Thread.currentThread().getName());
judge.await();
log.info("线程-{}开始跑步", Thread.currentThread().getName());
//进行跑步
TimeUnit.MILLISECONDS.sleep(score);//跑步需要花的时间
//运动员跑步完成
runner.countDown();
return score; //将执行时间返回
}
}
public static void main(String[] args) throws InterruptedException {
final int runnerNum = 8;
CountDownLatch judge = new CountDownLatch(1);//裁判
CountDownLatch runner = new CountDownLatch(runnerNum);//运动员
//新建一个线程池
ExecutorService executorService = Executors.newFixedThreadPool(runnerNum);
List<Future<Integer>> list = new ArrayList<>();
for (int i = 0; i < runnerNum; i++) {
//每个线程都开始运行了,但是都会运行到 judge.await();那里停住,等待judge的countDown方法
Future<Integer> submit = executorService.submit(new Runner(judge, runner));
list.add(submit);
}
/***
* 注意,我故意把关闭线程池的操作放在这里了,仍然可以获取到想要的结果
* 这是因为线程池并不会立即关闭,而是将任务执行完才会关闭
*/
executorService.shutdown();
//枪声响起
judge.countDown(); //预备,开始,跑!!!!
//等待所有的跑步者跑完
runner.await();
log.info("跑步结果:");
//所有跑步者的跑步结果
for (Future<Integer> future : list) {
try {
Integer res = future.get();
System.out.print(res + " ");
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
}
- 测试结果
END