Java 线程池分批调用
本文记录 Java 分批调用代码逻辑,虽然分批调用有很多种写法,但是考虑线程池提交任务执行、某个批次执行结果失败如何处理、某一批次的执行结果如何与原来的 task 对应等。考虑到这些细节,记录下分批处理的写法:
public class InvokeAllTest {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newFixedThreadPool(4);
//一组待查询的 poi, 假设1k个
List<Long> poiNeed2queryList = new ArrayList<>();
//拆分成 每批100个 查询
List<List<Long>> allShardPoiList = Lists.partition(poiNeed2queryList, 100);
List<Callable<List<String>>> tasks = new ArrayList<>();
for (List<Long> shardPoiList : allShardPoiList) {
Callable<List<String>> task = () -> queryName(shardPoiList);
tasks.add(task);
}
try {
//future 的顺序 与 tasks 中任务顺序保持一致
List<Future<List<String>>> futureList = threadPool.invokeAll(tasks, 100, TimeUnit.MILLISECONDS);
for (Future<List<String>> future : futureList) {
try {
//获取到这一批次的 查询结果
List<String> poiNameList = future.get();
//后续 处理逻辑
} catch (Exception e) {
//获取某一批次结果失败,做一些处理
}
}
} catch (Exception e) {
//所有批次调用失败,做一些处理
}
}
//模拟根据 poiId 查 poi名称
private static List<String> queryName(List<Long> poiList) {
return new ArrayList<>();
}
}
- guava 的 Lists.partition 能够方便地对 list 做分批
- 采用 invokeAll 将多个批次的查询,提交线程池任务,并发执行。并可以指定各批次的超时时间(100ms 超时, future.get时会抛出异常)。具体地:
- 任务执行过程中出现了异常,future.get 抛出:ExecutionException
- 任务被取消(比如 调用 future.cancel ),则 future.get 抛出:CancellationException
- 执行该任务的线程被其它线程请求中断,则抛出:InterruptedException
- 线程池执行结果 futureList 中 future 的顺序和 tasks 列表中的 task 顺序是保持一致的,方便:某一批次的任务与该批次的执行结果对应起来
- 需要考虑某一批次执行失败了,如何处理?for 循环中 try-catch 获取某一批次的执行结果,若获取失败,执行处理逻辑
- 需要考虑所有批次执行失败了,如何处理?
第二种批量执行的方式:
如果某一批次的任务执行完成了,可以立即获取执行结果,并基于“执行”结果进行处理。但是无法指定每一批次的执行超时时间。
//无法指定某一批次任务的超时超时
private static void completionServiceTest() {
ExecutorService threadPool = Executors.newFixedThreadPool(4);
ExecutorCompletionService<List<String>> completionService = new ExecutorCompletionService<>(threadPool);
//一组待查询的 poi, 假设1k个
List<Long> poiNeed2queryList = new ArrayList<>();
//拆分成 每批100个 查询
List<List<Long>> allShardPoiList = Lists.partition(poiNeed2queryList, 100);
for (List<Long> shardPoiList : allShardPoiList) {
Callable<List<String>> task = () -> queryName(shardPoiList);
//提交 一个批次 查询任务
completionService.submit(task);
}
try {
for (int i = 0; i < allShardPoiList.size(); i++) {
//若某一批次执行完成,能够立即获取结果. 如果所有批次都未执行完成, 则阻塞
final Future<List<String>> future = completionService.take();
try {
//在这里 future.get() 不会出现阻塞
final List<String> poiNameList = future.get();
//后续处理逻辑, 处理 poiNameList
} catch (Exception e) {
//获取某一批次结果失败,做一些处理
}
}
} catch (InterruptedException e) {
//take 方法异常处理
}
}
这篇文章列举了几种不同的分批调用处理方式:http://www.javabyexamples.com/submit-tasks-in-batch-using-executorservice