CompletableFuture 和 ExecutorService 任务拆分的简单例子

        假如我们有一个记录流水号的列表List<String> lshList = new ArrayList<>(); 此流水号时某一个map中的key,map中的value则是流水号对应的业务数据, 我们想将列表分为三份,通过异步任务分别处理他们,并将处理结果进行合并。

CompletableFuture的应用

1.分割流水号列表。

    //将此列表分成三份通过多线程方式去推数据
    List<List<String>> listsThree = splitThree(lshList);
    List<String> first = listsThree.get(0);
    List<String> second = listsThree.get(1);
    List<String> three = listsThree.get(2);


   /**
     * 将集合拆分为三份
     * @param list
     * @return
     */
    public List<List<String>> splitThree(List<String> list){
        int size = list.size();
        int firstPartSize = size / 3;
        int secondPartSize = (size % 3 == 0) ? firstPartSize : firstPartSize + 1;
        int thirdPartSize = size - firstPartSize - secondPartSize;
        return IntStream.range(0, 3)
                .mapToObj(i -> list.subList(
                        i == 0 ? 0 : (i == 1 ? firstPartSize : firstPartSize + secondPartSize),
                        i == 0 ? firstPartSize : (i == 1 ? firstPartSize + secondPartSize : size)
                ))
                .collect(Collectors.toList());
    }

2.将其三份业务数据根据流水号分别放在不同的map中。

        Map<String,ReqMasters> reqMastersFirstMap = new HashMap<>();
        Map<String,ReqMasters> reqMastersSecondHalfMap = new HashMap<>();
        Map<String,ReqMasters> reqMastersThreeHalfMap = new HashMap<>();
        // 分三份
        first.forEach(obj->{
            reqMastersFirstMap.put(obj,reqMastersMap.get(obj));
        });
        second.forEach(obj->{
            reqMastersSecondHalfMap.put(obj,reqMastersMap.get(obj));
        });
        three.forEach(obj->{
            reqMastersThreeHalfMap.put(obj,reqMastersMap.get(obj));
        });

3.创建三个异步任务,对这三份业务数据分别进行处理。

        // 创建第一个异步任务
        CompletableFuture<List<VerificationDto>> future1 = CompletableFuture.supplyAsync(() -> {
            //任务1
            return newOrder(reqMastersFirstMap,"Thread1");
        });

        // 创建第二个异步任务
        CompletableFuture<List<VerificationDto>> future2 = CompletableFuture.supplyAsync(() -> {
            //任务2
            return newOrder(reqMastersSecondHalfMap,"Thread2");
        });

        // 创建第三个异步任务
        CompletableFuture<List<VerificationDto>> future3 = CompletableFuture.supplyAsync(() -> {
            //任务2
            return newOrder(reqMastersThreeHalfMap,"Thread3");
        });

newOrder接口如下,实现类省略:

public List<VerificationDto> newOrder(Map<String, ReqMasters> reqMastersMap,String threadName);

4.等待其任务执行完毕后,将任务结果进行合并。

        // 使用CompletableFuture.allOf等待所有任务完成
        // 等待所有任务完成
        CompletableFuture<Void> allFutures = CompletableFuture.allOf(future1, future2, future3);

        // 等待所有任务完成,并获取结果 ,join方法会阻塞直到所有任务完成
        allFutures.join();
        // 合并任务结果
        List<List<VerificationDto>> lists = Arrays.asList(future1.join(), future2.join(), future3.join());

ExecutorService的应用

        假如我们有一个需要回传报告的列表,此回传报告任务无返回信息,异常信息都在任务内自动记表。

    List<LisOrder> finishLisOrderReport = new ArrayList<>();

LisOrder对象中有个属性threadNum提前记录它应该被分配到的线程组,现在我们根据threadNum对list进行划分。

        //根据threadnum对list进行划分
        Map<Integer, List<LisOrder>> groupedOrders = finishLisOrderReport.stream()
                .collect(Collectors.groupingBy(LisOrder::getThreadNum));

划分后我们根据这个map的大小(注意不是list的大小)创建一个固定的线程池。

        // 创建一个固定大小的线程池
        ExecutorService executorService = Executors.newFixedThreadPool(groupedOrders.size());

接下来我们把划分好的任务,提交给线程池,通过多线程的方式去执行:

        // groupedOrders 现在是一个Map,它的键是threadNum,值是对应的LisOrder列表
        for (Map.Entry<Integer, List<LisOrder>> entry : groupedOrders.entrySet()) {
           log.info("Thread " + entry.getKey());
            List<LisOrder> subList = entry.getValue();
            executorService.submit(() -> {
                try {
                    for (LisOrder order : subList) {
                        getReportFromLis(order);
                        log.info("推送报告数据:"+order.getLisTid());
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        }

        executorService.shutdown(); // 关闭线程池
getReportFromLis接口如下,具体实现类省略。
public Result<?> getReportFromLis(LisOrder lisOrder);

        不过如上的ExecutorService这样进行多任务划分有点脱裤子放屁并不能足以发挥多线程的威力,每个任务里的for循环还是同步执行的(好处就是将任务划分开了),如果任务列表很大其实还是只是作为几个子任务进行异步执行,我们也可以完全给改造成多线程的方式(不过这个也有缺点,没有提前控制可能会造成线程安全问题),Java中的多任务和多线程其实都是实现并发执行的方法,这里的例子的区别就是我提前把任务划分出来交给多线程去执行(能提前分组更好控制并发),我们还能这样写,不划分任务一股脑的都提交给线程池:

public class updateLisDataThread implements Callable<Boolean> {

    private LisOrder order;

    public updateLisDataThread(LisOrder order) {
        this.order = order;
    }


    @Override
    public Boolean call() throws Exception {
        // 这里是保存LisOrder到数据库的代码
        // 假设这个方法会返回true如果保存成功,false如果失败
        // 注意:这里的代码需要根据你的实际数据库操作来编写
        boolean success = updateLisData(order);
        return success;

    }

    private boolean updateLisData(LisOrder order) {
        // 示例:仅打印到控制台,实际中应替换为数据库保存操作
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("保存数据: " + order.getLisTid());
        System.out.println("----------");
        // 假设总是成功
        return true;
    }


    public static void main(String[] args) throws ExecutionException, InterruptedException {
        List<LisOrder> orders = new ArrayList<>();
        // 假设orders已经被填充了数据
        for (int i = 0; i < 30; i++) {
            LisOrder lisOrder = new LisOrder();
            lisOrder.setLisTid("000"+i);
            orders.add(lisOrder);
        }

        ExecutorService executorService = Executors.newFixedThreadPool(5); // 创建一个固定大小的线程池
        List<Future<Boolean>> futures = new ArrayList<>();

        for (LisOrder order : orders) {
            Future<Boolean> future = executorService.submit(new updateLisDataThread(order));
            futures.add(future);
        }

        // 等待所有任务完成并处理结果(如果需要的话)
        for (Future<Boolean> future : futures) {
            Boolean result = future.get(); // 这会阻塞直到任务完成
            if (!result) {
                // 处理保存失败的情况
                System.err.println("保存数据失败.");
            }
        }

        executorService.shutdown(); // 关闭线程池
        while (!executorService.isTerminated()) {
            // 等待线程池中的所有任务都完成
        }
        System.err.println("执行完毕.");
    }
}

如果线程池大小为5,同时间只有五个线程在运行吗?

        如果线程池的大小(也称为核心线程数或线程池的最大线程数,具体取决于线程池的类型和配置)被设置为5,那么在同一时刻,线程池中最多只有五个线程会处于活动状态(即正在执行任务)。当线程池接收到新任务时,如果当前线程池中的线程数还没有达到5个,它会创建一个新线程来执行任务。然而,如果线程池中的线程数已经达到5个,并且这些线程都在忙碌地执行任务,那么新提交的任务将会被放入队列中等待,直到有线程完成其当前任务并变为空闲状态。

还需要注意线程池的一些其他配置和特性:

  1. 队列:线程池通常使用一个队列来存储等待执行的任务。当线程池中的线程都在忙碌时,新任务会被放入队列中。队列的大小和类型(例如,有界队列或无界队列)会影响线程池的行为。
  2. 拒绝策略:如果线程池中的线程都在忙碌,并且队列已满(如果使用了有界队列),那么当再有新任务提交给线程池时,线程池会调用其拒绝策略来处理这个任务。Java的ThreadPoolExecutor提供了几种不同的拒绝策略,如抛出异常、直接丢弃任务(这时需要你下次再执行的时候把任务重新加回来)、丢弃队列中最老的任务或者将任务交给调用线程来执行。线程池的默认拒绝策略是 AbortPolicy。当线程池无法处理新任务时(即线程池中的线程都在忙碌,且任务队列已满),AbortPolicy 会直接抛出一个 RejectedExecutionException 异常。
  3. 最大线程数:除了核心线程数之外,ThreadPoolExecutor还允许设置一个最大线程数。当线程池中的线程数达到核心线程数时,如果队列也满了,线程池可以创建额外的线程(但不超过最大线程数)来处理新提交的任务。然而,这些额外的线程在空闲一段时间后(这个时间可以配置)会被终止,以将线程数缩减回核心线程数。

  • 7
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值