前言
CountDownLatch允许一个或多个线程等待其他线程完成操作。定义CountDownLatch的时候,需要传入一个正数来初始化计数器(虽然传入0也可以,但这样的话CountDownLatch没什么实际意义)。其countDown方法用于递减计数器,await方法会使当前线程阻塞,直到计数器递减为0。所以CountDownLatch常用于多个线程之间的协调工作。
示例
假设我们现在有这样一个需求:
由于很多群友在技术群搞黄色🍋,群主想了个办法
- 发布假资源,吸引🍋党的注意
- 统计名单
- 踢出群聊
过程如下所示:
public class CountDownLatchTest {
private static ExecutorService ExecutorService = Executors.newFixedThreadPool(2);
public static void main(String[] args) throws InterruptedException {
// 1. 模拟从数据库获取数据
String[] data = query();
System.out.println("获取群友名单");
// 2. 数据处理
IntStream.range(0, data.length).forEach(i -> {
ExecutorService.execute(() -> {
if (i==1){
data[i] = "Yellow";
}
String value = data[i];
System.out.println(value+"群友上车成功");
});
});
System.out.println("所有群友都处理完了");
// 关闭线程池
ExecutorService.shutdown();
// 3. 保存数据
save(data);
}
private static String[] query() {
return new String[]{"T", "B", "Y", "K"};
}
private static void save(String[] data) {
System.out.println("踢出群聊名单 - " + Arrays.toString(data));
}
}
由于线程获取CPU时间片的不确定性,所以有可能数据还没有处理完毕,导致没上勾就踢错
获取群友名单
所有群友都处理完了
踢出群聊名单 - [T, B, Y, K]
T群友上车成功
B群友上车成功
Y群友上车成功
K群友上车成功
CountDownLatch解决这个问题
public class CountDownLatchTest {
private static ExecutorService ExecutorService = Executors.newFixedThreadPool(2);
private static CountDownLatch latch = new CountDownLatch(4);
public static void main(String[] args) throws InterruptedException {
// 1. 模拟从数据库获取数据
String[] data = query();
System.out.println("获取群友名单");
// 2. 数据处理
IntStream.range(0, data.length).forEach(i -> {
ExecutorService.execute(() -> {
if (i==1){
data[i] = "Yellow";
}
String value = data[i];
System.out.println(value+"群友上车成功");
latch.countDown();
});
});
latch.await(2, TimeUnit.SECONDS); // 最多等待 3秒
System.out.println("所有群友都处理完了");
// 关闭线程池
ExecutorService.shutdown();
// 3. 保存数据
save(data);
}
private static String[] query() {
return new String[]{"T", "B", "Y", "K"};
}
private static void save(String[] data) {
System.out.println("踢出群聊名单 - " + Arrays.toString(data));
}
}
定义一个CountDownLatch,计数器值为4,在每个线程执行完毕的时候调用countDown方法,让计数器减1。
main方法调用await方法让main线程阻塞等待,直到计数器被减为0。所以这就保证了只有当所有数据加工完毕才执行保存数据操作。
await有重载方法:await(long timeout, TimeUnit unit),设置最大等待时间,超过这个时间程序将继续执行不再被阻塞
获取群友名单
T群友上车成功
Y群友上车成功
K群友上车成功
Yellow群友上车成功
所有群友都处理完了
踢出群聊名单 - [T, Yellow, Y, K]
ok,群主的钓鱼执法成功!🌰