Java线程池分批调用
Java 线程池分批调用
原文:https://www.cnblogs.com/hapjin/p/17568676.html
前言
本文记录 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 中任务顺序保持一致
// 阻塞在这一行,直到10个部任务全部结束,或者超过100ms后,才会继续往下执行
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能够方便地对数据集做切片。
-
采用invokeAll将多个批次的查询,提交线程池,并发执行。并可以指定各批次的超时时间(100ms 超时, future.get时会抛出异常),具体地:
1.任务执行过程中出现了异常,future.get 抛出:ExecutionException。
2.任务被取消(比如 调用 future.cancel ),则 future.get 抛出:CancellationException。
3.执行该任务的线程被其它线程请求中断,则抛出:InterruptedException。
-
线程池执行结果 futureList 中 future 的顺序和 tasks 列表中的 task 顺序是保持一致的,方便:某一批次的任务与该批次的执行结果对应起来。
-
需要考虑某一批次执行失败了,如何处理?for 循环中 try-catch 获取某一批次的执行结果,若获取失败,执行处理逻辑。
-
需要考虑所有批次执行失败了,如何处理?
写法二
使用ExecutorCompletionService类,如果某一批次的任务执行完成了,可以立即获取执行结果,既而基于“执行”结果进行后续处理:
由于写法一会阻塞在future.get()处,而本写法不会阻塞,所以本写法效率更高。
但是缺点是无法指定每一批次的执行超时时间。
结果和任务的顺序对应关系,也得不到保障。
//无法指定某一批次任务的超时超时
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
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
2020-08-30 git分支操作