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<>();
    }
}
  1. guava的 Lists.partition能够方便地对数据集做切片。

  2. 采用invokeAll将多个批次的查询,提交线程池,并发执行。并可以指定各批次的超时时间(100ms 超时, future.get时会抛出异常),具体地:

1.任务执行过程中出现了异常,future.get 抛出:ExecutionException。
2.任务被取消(比如 调用 future.cancel ),则 future.get 抛出:CancellationException。
3.执行该任务的线程被其它线程请求中断,则抛出:InterruptedException。

  1. 线程池执行结果 futureList 中 future 的顺序和 tasks 列表中的 task 顺序是保持一致的,方便:某一批次的任务与该批次的执行结果对应起来。

  2. 需要考虑某一批次执行失败了,如何处理?for 循环中 try-catch 获取某一批次的执行结果,若获取失败,执行处理逻辑。

  3. 需要考虑所有批次执行失败了,如何处理?

写法二

使用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

posted @   JaxYoun  阅读(261)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
历史上的今天:
2020-08-30 git分支操作
点击右上角即可分享
微信分享提示