JUC之CountDownLatch介绍及使用
CountDownLatch允许一个或多个线程等待其他线程完成操作。定义CountDownLatch的时候,需要传入一个正数来初始化计数器(虽然传入0也可以,但这样的话CountDownLatch没什么实际意义)。其countDown方法用于递减计数器,await方法会使当前线程阻塞,直到计数器递减为0。所以CountDownLatch常用于多个线程之间的协调工作。
CountDownLatch示例
假设我们现在有这样一个需求:
1.从数据库获取数据
2.对这批数据进行处理
3.保存这批数据
为了让程序执行效率更高,第2步中我们可以使用多线程来并行处理这批数据,大致过程如下所示:
public class CountDownLatchTest {
public static void main(String[] args) throws InterruptedException {
// 1. 模拟从数据库获取数据
int[] data = query();
System.out.println("获取数据完毕");
// 2. 数据处理
IntStream.range(0, data.length).forEach(i -> {
ExecutorService.execute(() -> {
System.out.println(Thread.currentThread() + "处理第" + (i + 1) + "条数据");
int value = data[i];
if (value % 2 == 0) {
data[i] = value * 2;
} else {
data[i] = value * 10;
}
});
});
System.out.println("所有数据都处理完了");
// 关闭线程池
ExecutorService.shutdown();
// 3. 保存数据
save(data);
}
private static int[] query() {
return new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
}
private static void save(int[] data) {
System.out.println("保存数据 - " + Arrays.toString(data));
}
}
由于线程获取CPU时间片的不确定性,所以有可能数据还没有处理完毕,第3步就执行完了:
获取数据完毕
所有数据都处理完了
保存数据 - [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Thread[pool-1-thread-2,5,main]处理第2条数据
Thread[pool-1-thread-1,5,main]处理第1条数据
Thread[pool-1-thread-2,5,main]处理第3条数据
Thread[pool-1-thread-1,5,main]处理第4条数据
Thread[pool-1-thread-1,5,main]处理第6条数据
Thread[pool-1-thread-2,5,main]处理第5条数据
Thread[pool-1-thread-1,5,main]处理第7条数据
Thread[pool-1-thread-1,5,main]处理第9条数据
Thread[pool-1-thread-2,5,main]处理第8条数据
Thread[pool-1-thread-1,5,main]处理第10条数据
我们可以借助CountDownLatch解决这个问题:
public class CountDownLatchTest {
private static ExecutorService ExecutorService = Executors.newFixedThreadPool(2);
private static CountDownLatch latch = new CountDownLatch(10);
public static void main(String[] args) throws InterruptedException {
// 1. 模拟从数据库获取数据
int[] data = query();
System.out.println("获取数据完毕");
// 2. 数据处理
IntStream.range(0, data.length).forEach(i -> {
ExecutorService.execute(() -> {
System.out.println(Thread.currentThread() + "处理第" + (i + 1) + "条数据");
int value = data[i];
if (value % 2 == 0) {
data[i] = value * 2;
} else {
data[i] = value * 10;
}
latch.countDown();
});
});
latch.await();
System.out.println("所有数据都处理完了");
// 关闭线程池
ExecutorService.shutdown();
// 3. 保存数据
save(data);
}
private static int[] query() {
return new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
}
private static void save(int[] data) {
System.out.println("保存数据 - " + Arrays.toString(data));
}
}
我们定义了一个CountDownLatch,计数器值为10,和数据量一致。然后在第2步中,当每个线程执行完毕的时候调用countDown方法,让计数器减1。在第3步前调用await方法让main线程阻塞等待,直到计数器被减为0。所以这就保证了只有当所有数据加工完毕才执行保存数据操作。
执行方法,程序输出如下所示:
获取数据完毕
Thread[pool-1-thread-1,5,main]处理第1条数据
Thread[pool-1-thread-1,5,main]处理第3条数据
Thread[pool-1-thread-1,5,main]处理第4条数据
Thread[pool-1-thread-1,5,main]处理第5条数据
Thread[pool-1-thread-1,5,main]处理第6条数据
Thread[pool-1-thread-1,5,main]处理第7条数据
Thread[pool-1-thread-1,5,main]处理第8条数据
Thread[pool-1-thread-1,5,main]处理第9条数据
Thread[pool-1-thread-1,5,main]处理第10条数据
Thread[pool-1-thread-2,5,main]处理第2条数据
所有数据都处理完了
保存数据 - [10, 4, 30, 8, 50, 12, 70, 16, 90, 20]
await有重载方法:await(long timeout, TimeUnit unit),设置最大等待时间,超过这个时间程序将继续执行不再被阻塞:
public class CountDownLatchTest {
public static void main(String[] args) throws InterruptedException {
final CountDownLatch latch = new CountDownLatch(1);
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(4);
System.out.println(Thread.currentThread() + "线程执行完毕");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
latch.countDown();
}
}, "thread1").start();
latch.await(3, TimeUnit.SECONDS); // 最多等待 3秒
System.out.println("main线程执行完毕");
}
}
输出如下:
main线程执行完毕
Thread[thread1,5,main]线程执行完毕
特别声明:
本文转载自Mrbird博客:https://mrbird.cc/JUC-CountDownLatch.html
波比大佬牛逼!