抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

java并发工具类-CountDownLatch

CountDownLatch的简介

CountDownLatch是一个同步工具类,它允许一个或多个线程一直等待,直到其他线程执行完后再执行。CountDownLatch是在java1.5被引入的,跟它一起被引入的并发工具类还有CyclicBarrier、Semaphore、 ConcurrentHashMap和BlockingQueue,它们都存在于JUC (java.util.concurrent)包下

CountDownLatch原理

​ CountDownLatch是通过一个计数器来实现的,计数器的初始化值为线程的数量。每当一个线程完成了自己的任务后,计数器的值就相应得减1(CountDownLatch.countDown()方法)。当计数器到达0时,表示所有的线程都已完成任务,,然后在闭锁上等待CountDownLatch.await()方法的线程就可以恢复执行任务。

注意:这是一个一次性操作 - 计数无法重置。 如果你需要一个重置的版本计数,考虑使用CyclicBarrier。

应用场景

实现最大的并行性:有时我们想同时启动多个线程,实现最大程度的并行性。例如,我们想测试一个单例类。如果我们创建一个初始计数为1的CountDownLatch,并让所有线程都在这个锁上等待,那么我们可以很轻松地完成测试。我们只需调用 一次countDown()方法就可以让所有的等待线程同时恢复执行。

开始执行前等待n个线程完成各自任务:例如应用程序启动类要确保在处理用户请求前,所有N个外部系统已经启动和运行了,例如处理excel中多个表单。

注意:一个线程不一定只能做countDown一次,也可以countDown多次

CountDownLatch的示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class CountDownLatchTest {
private static ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 10, 100, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(5));

private static Random random = new Random();


public static void execute(CountDownLatch countDownLatch) {
//获取一个随机数
long sleepTime = random.nextInt(10);
long threadId = Thread.currentThread().getId();
System.out.println("线程ID" + threadId + ",开始执行--countDown");

try {
//睡眠随机秒
Thread.sleep(sleepTime * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//计数器减1
countDownLatch.countDown();
System.out.println("线程ID" + threadId + ",准备任务完成耗时:" + sleepTime + "当前时间" + System.currentTimeMillis());
try {
//线程等待其他任务完成后唤醒
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程ID" + threadId + ",开始执行任务,当前时间:" + System.currentTimeMillis());
}

public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(5);
for (int i = 0; i < 5; i++) {
threadPool.submit(() -> {
execute(countDownLatch);
});
}
//线程等待其他任务完成后唤醒
countDownLatch.await();
Thread.sleep(1000);
threadPool.shutdown();
System.out.println("全部任务执行完成");
}
}

打印结果

线程ID13,开始执行–countDown
线程ID16,开始执行–countDown
线程ID15,开始执行–countDown
线程ID12,开始执行–countDown
线程ID14,开始执行–countDown
线程ID14,准备任务完成耗时:3当前时间1565159118048
线程ID16,准备任务完成耗时:4当前时间1565159119047
线程ID12,准备任务完成耗时:4当前时间1565159119048
线程ID15,准备任务完成耗时:6当前时间1565159121047
线程ID13,准备任务完成耗时:7当前时间1565159122048
线程ID13,开始执行任务,当前时间:1565159122048
线程ID14,开始执行任务,当前时间:1565159122048
线程ID12,开始执行任务,当前时间:1565159122048
线程ID16,开始执行任务,当前时间:1565159122048
线程ID15,开始执行任务,当前时间:1565159122049
全部任务执行完成

使用CountDownLatch压测

​ 在实战项目中,我们除了使用 jemter 等工具进行压测外,还可以自己动手使用 CountDownLatch 类编写压测代码。可以说 jemter 的并发压测背后也是使用的 CountDownLatch。可见掌握 CountDownLatch 类的使用是有多么的重要。

CountDownLatch是Java多线程同步器的四大金刚之一,CountDownLatch能够使一个线程等待其他线程完成各自的工作后再执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
private  void latchTest() throws InterruptedException {
//压测线程数
int testThreads = 300;
final CountDownLatch start = new CountDownLatch(1);
final CountDownLatch end = new CountDownLatch(testThreads);
//创建线程池
ExecutorService exce = Executors.newFixedThreadPool(testThreads);
for (int i = 0; i < testThreads; i++) {
exce.submit(() -> {
try {
//启动后等待 唤醒
start.await();
//压测具体方法
testLoad();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//结束CountDownLatch -1
end.countDown();
}
});

}
//连接池线程初始化完成 开始压测
start.countDown();
//压测完成后结束
end.await();
exce.shutdown();
}

简简单单的几行代码就可以实现300的压测。

评论