文章目录
概述
在Java多线程编程中,经常会遇到需要等待所有提交的线程执行完任务后再执行某个操作的场景。这种需求可以分为两种情况:一种是任务数量已知,另一种是任务数量未知。本文将详细探讨这两种场景下的最佳实践。
场景一:任务数量已知
使用 CountDownLatch
等待所有任务完成
在任务数量已知的情况下,例如对一个固定大小的列表进行多线程处理,我们可以使用 CountDownLatch
来实现线程同步。
示例代码
import java.util.concurrent.*;
public class CountDownLatchExample {
public static void main(String[] args) throws InterruptedException {
int numberOfTasks = 5;
CountDownLatch latch = new CountDownLatch(numberOfTasks);
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(4,8,60, TimeUnit.SECONDS,new ArrayBlockingQueue<>(200));
for (int i = 0; i < numberOfTasks; i++) {
threadPoolExecutor.submit(() -> {
try {
// 模拟任务处理
System.out.println("Task executed by " + Thread.currentThread().getName());
} finally {
latch.countDown();
}
});
}
// 等待所有任务完成
latch.await();
System.out.println("All tasks completed. Proceeding to the next step.");
// TODO: 2025/1/1 做一些任务完成之后的事情
}
}
注意事项
- 在任务处理时,一定要使用
try{}finally{}
,在任务执行完成后,执行latch.countDown()
使计数器减一。 - 要注意线程池配置的拒绝策略,如果由于线程池拒绝抛出异常,导致
latch.countDown()
没有被预期执行,则会导致latch.await()
一直等待。因此,在不允许任务丢失的场景中,最好使用new ThreadPoolExecutor.CallerRunsPolicy()
拒绝策略,丢到主线程执行,防止任务丢失而导致程序无法终止。
场景二:任务数量未知
场景:如读取一个表格数据,对表格所有数据处理完后写入到一个list里,再将list写入文件。
这里假设 numberOfTasks
数量是未知的。
方式一:使用Feature
等待
这种方式的优点在于,你可以自定义线程池拒绝策略。在某些场景下,线程池处理不了的,你可能本身就不想要这个结果了,你只需等待任务提交成功的结果即可。
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
public class CountDownLatchExample {
public static void main(String[] args) throws InterruptedException {
int numberOfTasks = 5;
CountDownLatch latch = new CountDownLatch(numberOfTasks);
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(4,8,60, TimeUnit.SECONDS,new ArrayBlockingQueue<>(200),new ThreadPoolExecutor.CallerRunsPolicy());
List<Future> futures = new ArrayList<>();
for (int i = 0; i < numberOfTasks; i++) {
Future<?> future = threadPoolExecutor.submit(() -> {
try {
// 模拟任务处理
System.out.println("Task executed by " + Thread.currentThread().getName());
} finally {
latch.countDown();
}
});
futures.add(future);
}
// 等待所有任务完成
for (Future future : futures) {
try {
future.get();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
System.out.println("All tasks completed. Proceeding to the next step.");
// TODO: 2025/1/1 做一些任务完成之后的事情
}
}
注意事项
这种方式需要注意的点是,在提交任务时进行异常捕获,防止因提交任务异常而导致的程序中断。
方式二:关闭线程池并等待任务执行完成
在任务数量未知的情况下,也可以通过线程池提交任务后,关闭线程池并等待任务执行完成,这也是我在工作中比较常用的方法。这种方法通常使用 ExecutorService
的 shutdown()
和 awaitTermination()
方法来实现。
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class CountDownLatchExample {
public static void main(String[] args) throws InterruptedException {
int numberOfTasks = 5;
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(4,8,60, TimeUnit.SECONDS,new ArrayBlockingQueue<>(200),new ThreadPoolExecutor.CallerRunsPolicy());
for (int i = 0; i < numberOfTasks; i++) {
threadPoolExecutor.submit(() -> {
// 模拟任务处理
System.out.println("Task executed by " + Thread.currentThread().getName());
});
}
threadPoolExecutor.shutdown();
// 等待所有任务完成(任务完成,或者到了超时实践)
if(threadPoolExecutor.awaitTermination(1,TimeUnit.HOURS)){
System.out.println("All tasks completed or waitTimeout. Proceeding to the next step.");
// TODO: 2025/1/1 做一些任务完成之后的事情
}
}
}
注意事项
要注意线程池配置的拒绝策略,如果由于线程池拒绝抛出异常,导致latch.countDown()
没有被预期执行,则会导致latch.await()
一直等待。因此,在不允许任务丢失的场景中,最好使用new ThreadPoolExecutor.CallerRunsPolicy()
拒绝策略,丢到主线程执行,防止任务丢失而导致程序无法终止。