CompletableFuture实践踩坑经验---美团技术分享

原文地址:CompletableFuture原理与实践

4.1 线程阻塞问题
4.1.1 代码执行在哪个线程上?

要合理治理线程资源,最基本的前提条件就是要在写代码时,清楚地知道每一行代码都将执行在哪个线程上。下面我们看一下CompletableFuture的执行线程情况。

CompletableFuture实现了CompletionStage接口,通过丰富的回调方法,支持各种组合操作,每种组合场景都有同步和异步两种方法。

同步方法(即不带Async后缀的方法)有两种情况。

如果注册时被依赖的操作已经执行完成,则直接由当前线程执行。

如果注册时被依赖的操作还未执行完,则由回调线程执行。

异步方法(即带Async后缀的方法):可以选择是否传递线程池参数Executor运行在指定线程池中;当不传递Executor时,会使用ForkJoinPool中的共用线程池CommonPool(CommonPool的大小是CPU核数-1,如果是IO密集的应用,线程数可能成为瓶颈)。

例如:
 

ExecutorService threadPool1 = new ThreadPoolExecutor(10, 10, 0L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(100));
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
    System.out.println("supplyAsync 执行线程:" + Thread.currentThread().getName());
    //业务操作
    return "";
}, threadPool1);
//此时,如果future1中的业务操作已经执行完毕并返回,则该thenApply直接由当前main线程执行;否则,将会由执行以上业务操作的threadPool1中的线程执行。
future1.thenApply(value -> {
    System.out.println("thenApply 执行线程:" + Thread.currentThread().getName());
    return value + "1";
});
//使用ForkJoinPool中的共用线程池CommonPool
future1.thenApplyAsync(value -> {
//do something
  return value + "1";
});
//使用指定线程池
future1.thenApplyAsync(value -> {
//do something
  return value + "1";
}, threadPool1);

4.2 线程池须知


4.2.1 异步回调要传线程池

前面提到,异步回调方法可以选择是否传递线程池参数Executor,这里我们建议强制传线程池,且根据实际情况做线程池隔离。

当不传递线程池时,会使用ForkJoinPool中的公共线程池CommonPool,这里所有调用将共用该线程池,核心线程数=处理器数量-1(单核核心线程数为1),所有异步回调都会共用该CommonPool,核心与非核心业务都竞争同一个池中的线程,很容易成为系统瓶颈。手动传递线程池参数可以更方便的调节参数,并且可以给不同的业务分配不同的线程池,以求资源隔离,减少不同业务之间的相互干扰。

4.2.2 线程池循环引用会导致死锁
主任务-子任务同用一个线程池threadPool1

public Object doGet() {
  ExecutorService threadPool1 = new ThreadPoolExecutor(10, 10, 0L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(100));
  CompletableFuture cf1 = CompletableFuture.supplyAsync(() -> {
  //do sth
    return CompletableFuture.supplyAsync(() -> {
        System.out.println("child");
        return "child";
      }, threadPool1).join();//子任务
    }, threadPool1);
  return cf1.join();
}

如上代码块所示,doGet方法第三行通过supplyAsync向threadPool1请求线程,并且内部子任务又向threadPool1请求线程。threadPool1大小为10,当同一时刻有10个请求到达,则threadPool1被打满,子任务请求线程时进入阻塞队列排队,但是父任务的完成又依赖于子任务,这时由于子任务得不到线程,父任务无法完成。主线程执行cf1.join()进入阻塞状态,并且永远无法恢复。

为了修复该问题,需要将父任务与子任务做线程池隔离,两个任务请求不同的线程池,避免循环依赖导致的阻塞。

4.2.3 异步RPC调用注意不要阻塞IO线程池

服务异步化后很多步骤都会依赖于异步RPC调用的结果,这时需要特别注意一点,如果是使用基于NIO(比如Netty)的异步RPC,则返回结果是由IO线程负责设置的,即回调方法由IO线程触发,CompletableFuture同步回调(如thenApply、thenAccept等无Async后缀的方法)如果依赖的异步RPC调用的返回结果,那么这些同步回调将运行在IO线程上,而整个服务只有一个IO线程池,这时需要保证同步回调中不能有阻塞等耗时过长的逻辑,否则在这些逻辑执行完成前,IO线程将一直被占用,影响整个服务的响应。
————————————————
版权声明:本文为CSDN博主「美团技术团队」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/MeituanTech/article/details/124743345

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 好的,以下是使用CompletableFuture实现将1-10拆分、异步处理每个子集并进行合计的示例代码: ```java import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; public class CompletableFutureExample { public static void main(String[] args) throws Exception { List<Integer> list = new ArrayList<>(); for (int i = 1; i <= 10; i++) { list.add(i); } List<CompletableFuture<Integer>> futures = new ArrayList<>(); int batchSize = 2; // 每个子集的大小为2 for (int i = 0; i < list.size(); i += batchSize) { int start = i; int end = Math.min(i + batchSize, list.size()); List<Integer> sublist = list.subList(start, end); CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> { int sum = 0; for (int num : sublist) { sum += num; } return sum; }); futures.add(future); } CompletableFuture<Void> allFutures = CompletableFuture.allOf( futures.toArray(new CompletableFuture[futures.size()])); CompletableFuture<List<Integer>> results = allFutures.thenApply(v -> { List<Integer> resultList = new ArrayList<>(); for (CompletableFuture<Integer> future : futures) { resultList.add(future.join()); } return resultList; }); List<Integer> finalResult = results.get(); int totalSum = 0; for (int num : finalResult) { totalSum += num; } System.out.println("Total sum: " + totalSum); } } ``` 此代码中,我们将1-10添加到列表中,并使用batchSize = 2将其拆分成大小为2的子集,然后使用CompletableFuture异步处理每个子集中的数字求和。通过将所有CompletableFuture合并在一起,并使用allOf方法创建一个包含所有Future的新Future,然后使用thenApply方法来提取每个Future的结果,并将结果存储在一个新列表中。最后,我们对所有结果求和并打印总和。 希望这可以帮助您理解如何使用CompletableFuture对任务进行分割和异步处理。 ### 回答2: CompletableFutureJava8新增的一个类,可以用于实现异步编程。我们可以将集合1-10拆分成多个子集合,并使用CompletableFuture方法进行异步计算,最后将结果放入一个新的集合中。 首先,我们可以使用IntStream.rangeClosed()方法生成1-10的集合。然后,使用IntStream的方法将集合拆分成多个子集合,比如每个子集合包含1个元素。 接下来,我们可以使用CompletableFuture的静态方法supplyAsync()创建一个异步任务,计算每个子集合的合计,并将结果放入新的集合中。 我们可以使用lambda表达式将计算任务传递给supplyAsync()方法。在lambda表达式中,我们可以使用stream()方法计算子集合的合计,并使用reduce()方法将所有子集合的合计相加。最后,使用get()方法获取计算结果,并将结果放入新的集合中。 最后,使用CompletableFuture方法allOf()等待所有异步任务完成。之后,我们可以使用CompletableFuture方法join()获取最终的结果集合。 ### 回答3: 使用CompletableFuture可以很方便地实现将集合1-10拆分,并异步进行每个子集合的合计算,并将结果放入一个新的集合中的需求。 首先,我们可以使用CompletableFuture的静态方法supplyAsync创建一个异步任务,这个任务会返回一个子集合的合计算结果。我们可以将这些异步任务放入一个List中。 接下来,我们可以使用CompletableFuture的静态方法allOf创建一个新的异步任务,这个任务会等待所有子任务完成。我们可以使用thenApply方法在所有子任务完成后处理每个子任务的结果,并将结果放入一个新的集合中。 具体的代码如下: List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); List<CompletableFuture<Integer>> futures = new ArrayList<>(); // 拆分集合并异步进行每个子集合的合计算 for (int i = 0; i < numbers.size(); i += 2) { List<Integer> subset = numbers.subList(i, Math.min(i + 2, numbers.size())); CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> { int sum = 0; for (int number : subset) { sum += number; } return sum; }); futures.add(future); } // 等待所有子任务完成,并将结果放入新的集合中 CompletableFuture<Void> allFutures = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])); CompletableFuture<List<Integer>> resultFuture = allFutures.thenApply(v -> { List<Integer> result = new ArrayList<>(); for (CompletableFuture<Integer> future : futures) { try { Integer sum = future.get(); result.add(sum); } catch (InterruptedException | ExecutionException e) { // 异常处理 } } return result; }); List<Integer> result = resultFuture.join(); System.out.println(result); 这样就可以使用CompletableFuture将集合1-10拆分,拆分后异步进行每个子集合的合计算,并将结果放入一个新的集合中。最终输出结果为[3, 7, 11, 15, 9]。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值