CompletableFuture介绍与实战

CompletableFuture 介绍与实战

一、前言

​ 日常工作中,大多数情况下我们的接口的执行逻辑都是串行化的,串行化的逻辑也基本能满足我们绝大部分的场景。但是,在一些情况下我们的代码可能会存在一些比较耗时的操作,串行的逻辑就有可能造成代码的阻塞,影响用户的体验。这种情况下就需要我们对一些复杂的场景,耗时的操作做一些异步并行的操作,来提升代码的执行效率,从而提升用户的体验。

​ 在我们项目中就有这样一种场景,在一个报表的统计中,我们需要同时计算相关指标的当期值,上期值,同比,环比等。这就需要我们根据不同的时间段查询出相应的数据,然后在进行同比,环比等计算,每次请求接口都涉及2 - 3查询。随着业务的增长,数据量的增加接口的响应时间也是越来越慢。时间区间跨度选择越大,接口响应越慢。针对这种情况,我们采用了多线程并行查询不同时间段的数据的操作,接口的效率也是有了肉眼可见的提升。

​ 还有在复杂的查询中可能需要同时需要查询多个对象的信息返回给页面。A,B,C,使用串行查询的耗时就是A + B + C的耗时,使用并行查询的话耗时就是A,B,C中耗时最久的一个查询。

  • 串行查询

在这里插入图片描述

  • 并行查询

在这里插入图片描述

二、CompletableFuture 介绍

2.1 概述

CompletableFuture类是Java8引入的,提供了灵活的异步处理的能力,同时CompletableFuture支持对任务的编排的能力。

在这里插入图片描述

public class CompletableFuture<T> implements Future<T>, CompletionStage<T> {
}

CompletableFuture实现了FutureCompletionStage接口。

这里值得说一点的是在使用CompletableFuture进行异步任务处理的时候,如果不指定线程池的话,默认会使用ForkJoinPool.commonPool()作为线程池。

    public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) {return asyncSupplyStage(asyncPool, supplier);}private static final boolean useCommonPool =(ForkJoinPool.getCommonPoolParallelism() > 1);/*** Default executor -- ForkJoinPool.commonPool() unless it cannot* support parallelism.*/private static final Executor asyncPool = useCommonPool ?ForkJoinPool.commonPool() : new ThreadPerTaskExecutor();

异步执行的,默认线程池是ForkJoinPool.commonPool(),但为了业务之间互不影响,且便于定位问题,强烈推荐使用自定义线程池

三、常用功能

环境准备

@Bean(name = "customerExecutor")
public Executor customerExecutor() {log.info("start customerExecutor");ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();// 核心线程数executor.setCorePoolSize(4);// 最大线程数executor.setMaxPoolSize(6);// 任务队列大小executor.setQueueCapacity(20);// 线程名称前缀// executor.setThreadNamePrefix("completableFuture-async-");// 配置线程工厂executor.setThreadFactory(namedThreadFactory);// 拒绝策略// rejection-policy:当pool已经达到max size的时候,如何处理新任务// CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());// 初始化executor.initialize();return executor;
}

3.1 异步操作supplyAsyncrunAsync

3.1.1 功能介绍

CompletableFuture提供了四个静态方法来执行异步的任务。

    /*** 有返回值,使用默认线程池*/public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) {return asyncSupplyStage(asyncPool, supplier);}/*** 有返回值,使用自定义线程池(推荐使用)*/public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier,Executor executor) {return asyncSupplyStage(screenExecutor(executor), supplier);}/*** 无返回值,使用默认线程池*/public static CompletableFuture<Void> runAsync(Runnable runnable) {return asyncRunStage(asyncPool, runnable);}/*** 无返回值,使用自定义线程池(推荐使用)*/public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor) {return asyncRunStage(screenExecutor(executor), runnable);}
  • runAsync()Runnable函数式接口类型为参数,没有返回结果,

  • supplyAsync()Supplier函数式接口类型为参数,返回结果类型为U;Supplier接口的 get()是有返回值的(会阻塞)

  • 使用没有指定线程池Executor的方法时,内部使用ForkJoinPool.commonPool() 作为它的线程池执行异步代码。如果指定线程池,则使用指定的线程池运行。

  • 默认情况下CompletableFuture会使用公共的ForkJoinPool线程池,这个线程池默认创建的线程数是 CPU 的核数(也可以通过 JVM option:-Djava.util.concurrent.ForkJoinPool.common.parallelism 来设置ForkJoinPool线程池的线程数)。

  • 如果所有CompletableFuture共享一个线程池,那么一旦有任务执行一些很慢的 I/O 操作,就会导致线程池中所有线程都阻塞在 I/O 操作上,从而造成线程饥饿,进而影响整个系统的性能。所以,强烈建议你要根据不同的业务类型创建不同的线程池,以避免互相干扰

3.1.2 功能演示
3.1.2.1 supplyAsync()
  • 代码演示
public static void main(String[] args) {// 自定义线程池Executor executor = new ExecutorConfig().customerExecutor();// supplyAsync-自定义线程池CompletableFuture<String> supplyAsyncCustomer = CompletableFuture.supplyAsync(() -> {return "supplyAsync-customer:" + Thread.currentThread().getName();}, executor);// supplyAsync-默认线程池CompletableFuture<String> supplyAsyncDefault = CompletableFuture.supplyAsync(() -> {return "supplyAsync-default:" + Thread.currentThread().getName();});// 获取结果  join()和get()方法都是用来获取CompletableFuture异步之后的返回值。// join()方法抛出的是uncheck异常(即未经检查的异常),不会强制开发者抛出。// get()方法抛出的是经过检查的异常,ExecutionException, InterruptedException 需要用户手动处理(抛出或者 try catch)System.out.println(supplyAsyncCustomer.join());System.out.println(supplyAsyncDefault.join());try {String result1 = supplyAsyncCustomer.get();String result2 = supplyAsyncDefault.get();System.out.println(result1);System.out.println(result2);} catch (InterruptedException e) {throw new RuntimeException(e);} catch (ExecutionException e) {throw new RuntimeException(e);}
}
  • 输出结果
supplyAsync-customer:customer-executor-pool-0
supplyAsync-default:ForkJoinPool.commonPool-worker-9
supplyAsync-customer:customer-executor-pool-0
supplyAsync-default:ForkJoinPool.commonPool-worker-9

注意:

  1. join()get() 方法都可以获取CompletableFuture异步执行结果的返回值**(阻塞调用者线程,等待异步线程返回结果)。**。
  2. get() 方法抛出可检查异常ExecutionException, InterruptedException,需要抛出throw或者捕获try/catch异常。
  3. join() 抛出不可检查异常。
3.1.2.2 runAsync()代码演示
  • 代码演示
public static void main(String[] args) {// 自定义线程池Executor executor = new ExecutorConfig().customerExecutor();// runAsync-自定义线程池CompletableFuture.runAsync(() -> System.out.println("runAsync-customer:" + Thread.currentThread().getName()), executor);// runAsync-默认线程池CompletableFuture.runAsync(() -> System.out.println("runAsync-default:" + Thread.currentThread().getName()));}
  • 输出结果
runAsync-customer:customer-executor-pool-1
runAsync-default:ForkJoinPool.commonPool-worker-9

3.2 处理异步操作的结果

当我们采用异步操作进行任务处理时,拿到异步任务的结果后还可以对结果进行进一步的处理和操作,常用的方法有下面几个:

  • thenApply()
  • thenAccept()
  • thenRun()
  • whenComplete()

将上一段任务的执行结果作为下一阶段任务的入参参与重新计算,产生新的结果。

3.2.1 thenApply()
3.2.1.1 功能介绍

thenApply接收一个函数作为参数,使用该函数处理上一个CompletableFuture调用的结果,并返回一个具有处理结果的Future对象。

// 使用上一个任务的结果,沿用上一个任务的线程池
public <U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn);
// 使用默认线程池
public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn);
// 使用自定义线程池
public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn, Executor executor) {return uniApplyStage(screenExecutor(executor), fn);
}
3.2.1.2 功能演示
  • 代码演示
// thenApply
CompletableFuture<Integer> thenApply = CompletableFuture.supplyAsync(() -> {int result = 999;System.out.println("第一次运算的result: " + result);return result;}).thenApply(result -> {result = result * 2;System.out.println("第二次运算的result: " + result);return result;});
System.out.println("最终结果:" + thenApply.join());// thenApplyAsync 默认线程池
CompletableFuture<Integer> thenApplyAsyncDefault = CompletableFuture.supplyAsync(() -> {int result = 999;System.out.println("第一次运算,thread= "+ Thread.currentThread().getName()+" result= " + result);return result;}).thenApplyAsync(result -> {result = result * 2;System.out.println("第二次运算,thread= "+ Thread.currentThread().getName()+" result= " + result);return result;});
System.out.println("最终结果:,thread= "+ Thread.currentThread().getName()+" result= " + thenApplyAsyncDefault.join());// thenApplyAsync自定义线程池
CompletableFuture<Integer> thenApplyAsyncCustomer = CompletableFuture.supplyAsync(() -> {int result = 999;System.out.println("第一次运算,thread= " + Thread.currentThread().getName() + " result= " + result);return result;}).thenApplyAsync(result -> {result = result * 2;System.out.println("第二次运算,thread= " + Thread.currentThread().getName() + " result= " + result);return result;}, executor);
System.out.println("最终结果:,thread= " + Thread.currentThread().getName() + " result= " + thenApplyAsyncCustomer.join());
  • 输出结果
第一次运算的result: 999
第二次运算的result: 1998
最终结果:1998第一次运算,thread= ForkJoinPool.commonPool-worker-9 result= 999
第二次运算,thread= ForkJoinPool.commonPool-worker-9 result= 1998
最终结果:,thread= main result= 1998第一次运算,thread= ForkJoinPool.commonPool-worker-9 result= 999
第二次运算,thread= customer-executor-pool-0 result= 1998
最终结果:,thread= main result= 1998
3.2.2 thenAccept()thenRun()

如果你不需要从回调函数中获取返回结果,可以使用 thenAccept() 或者 thenRun()

这两个方法的区别在于 thenRun() 不能访问异步计算的结果。

3.2.2.1 功能介绍
  • thenAccept()
public CompletableFuture<Void> thenAccept(Consumer<? super T> action) {return uniAcceptStage(null, action);
}
public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action) {return uniAcceptStage(defaultExecutor(), action);
}
public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action,Executor executor) {return uniAcceptStage(screenExecutor(executor), action);
}
  • thenRun()
public CompletableFuture<Void> thenRun(Runnable action) {return uniRunStage(null, action);
}
public CompletableFuture<Void> thenRunAsync(Runnable action) {return uniRunStage(defaultExecutor(), action);
}
public CompletableFuture<Void> thenRunAsync(Runnable action,Executor executor) {return uniRunStage(screenExecutor(executor), action);
}
3.2.2.2 功能演示
  • 代码演示
// thenAccept
CompletableFuture<Void> thenAccept = CompletableFuture.supplyAsync(() -> {int result = 100;System.out.println("第一次运算:" + result);return result;}).thenAccept(result ->System.out.println("第二次运算:" + result * 5));
System.out.println("thenAccept = " + thenAccept.join());
// thenRun
CompletableFuture<Void> thenRun = CompletableFuture.supplyAsync(() -> {int result = 100;System.out.println("第一次运算:" + result);return result;}).thenRun(() ->System.out.println("第二次运算:不接收不处理第一次的运算结果!" ));
System.out.println("thenRun = " + thenRun.join());
  • 输出结果
第一次运算:100
第二次运算:500
thenAccept = null第一次运算:100
第二次运算:不接收不处理第一次的运算结果!
thenRun = null
3.2.3 whenComplete()
3.2.3.1 功能介绍

CompletableFuture的计算结果完成,或者抛出异常的时候,我们可以执行特定的 Action。主要是下面的方法:

public CompletableFuture<T> whenComplete(BiConsumer<? super T, ? super Throwable> action) {return uniWhenCompleteStage(null, action);
}
// 使用默认线程池
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T, ? super Throwable> action) {return uniWhenCompleteStage(defaultExecutor(), action);
}
// 使用自定义线程池(推荐)
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T, ? super Throwable> action, Executor executor) {return uniWhenCompleteStage(screenExecutor(executor), action);
}
3.2.3.2 功能演示
  • 代码演示
// whenComplete 无异常
CompletableFuture<Integer> whenComplete = CompletableFuture.supplyAsync(() -> {int result = 100;System.out.println("第一次运算:" + result);return result;}).whenComplete((result, ex) ->System.out.println("第二次运算:" + result * 5));
System.out.println("whenComplete = " + whenComplete.join());// whenComplete 异常
CompletableFuture<Integer> whenCompleteException = CompletableFuture.supplyAsync(() -> {int result = 100;System.out.println("第一次运算:" + result);int i = 1 / 0;return result;}).whenComplete((result, ex) -> {System.out.println("处理异常为:" + ex);System.out.println("第二次运算:" + result * 5);
});
System.out.println("whenCompleteException = " + whenCompleteException.join());
  • 输出结果
第一次运算:100
第二次运算:500
whenComplete = 100第一次运算:100
处理异常为:java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
Exception in thread "main" java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zeroat java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:273)at java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:280)at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1592)at java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1582)at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)
Caused by: java.lang.ArithmeticException: / by zeroat com.itender.threadpool.completablefuture.CompletableFutureIntro.lambda$main$2(CompletableFutureIntro.java:131)at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1590)... 5 more

3.3 异常处理

当我们采用异步操作进行任务处理时,中间如果有任务发生了异常,我们可以通过下面几种方式进行处理:

  • handle()

handle 是执行任务完成时对结果的处理, handle 方法和 thenApply 方法处理方式大致一样,不同的是 handle 是在任务完成后再执行且Handle可以根据可以根据任务是否有异常来进行做相应的后续处理操作。

  • exceptionally()

exceptionally也可以用来处理异常,发生异常后,执行exceptionally方法。

  • whenComplete()

whenComplete()方法前面已经演示过了,这里就不做过多的赘述了。

3.3.1 功能介绍
  • handle()
public <U> CompletableFuture<U> handle(BiFunction<? super T, Throwable, ? extends U> fn) {return uniHandleStage(null, fn);
}public <U> CompletableFuture<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn) {return uniHandleStage(defaultExecutor(), fn);
}public <U> CompletableFuture<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn, Executor executor) {return uniHandleStage(screenExecutor(executor), fn);
}
  • exceptionally()
public CompletableFuture<T> exceptionally(Function<Throwable, ? extends T> fn) {return uniExceptionallyStage(fn);}
3.3.2 功能演示
  • 代码演示
// handle
CompletableFuture<Integer> handle = CompletableFuture.supplyAsync(() -> {int result = 100;System.out.println("第一次运算:" + result);int i = 1 / 0;return result;}).handle((result, ex) -> {System.out.println("处理异常为:" + ex.getMessage());System.out.println("第二次运算:" + result * 5);return result * 5;
});
System.out.println("handle = " + handle.join());// CompletableFuture<String> future
//         = CompletableFuture.supplyAsync(() -> {
//     if (true) {
//         throw new RuntimeException("Computation error!");
//     }
//     return "hello!";
// }).handle((res, ex) -> {
//     // res 代表返回的结果
//     // ex 的类型为 Throwable ,代表抛出的异常
//     return res != null ? res : "world!";
// });
// System.out.println("future = " + future.join());// exceptionally 异常
CompletableFuture<Integer> exceptionally = CompletableFuture.supplyAsync(() -> {int result = 100;System.out.println("第一次运算:" + result);int i = 1 / 0;return result;}).exceptionally(ex -> {System.out.println("处理异常为:" + ex.getMessage());return 100;
});
System.out.println("exceptionally = " + exceptionally.join());
  • 输出结果

此处handle和exceptionally结果为分开输出的

第一次运算:100
处理异常为:java.lang.ArithmeticException: / by zero
Exception in thread "main" java.util.concurrent.CompletionException: java.lang.NullPointerExceptionat java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:273)at java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:280)at java.util.concurrent.CompletableFuture.uniHandle(CompletableFuture.java:824)at java.util.concurrent.CompletableFuture$UniHandle.tryFire(CompletableFuture.java:797)at java.util.concurrent.CompletableFuture.uniHandleStage(CompletableFuture.java:837)at java.util.concurrent.CompletableFuture.handle(CompletableFuture.java:2155)at com.itender.threadpool.completablefuture.CompletableFutureIntro.main(CompletableFutureIntro.java:147)
Caused by: java.lang.NullPointerExceptionat com.itender.threadpool.completablefuture.CompletableFutureIntro.lambda$main$1(CompletableFutureIntro.java:149)at java.util.concurrent.CompletableFuture.uniHandle(CompletableFuture.java:822)... 4 more第一次运算:100
处理异常为:java.lang.ArithmeticException: / by zero
exceptionally = 100

3.4 结果组合

提供了可以组合任务的方法。

3.4.1 功能介绍
  • thenCombine()

thenCombine 会在两个CompletableFuture任务都执行完成后,把两个任务的结果一块处理。

public <U,V> CompletionStage<V> thenCombine(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn);public <U,V> CompletionStage<V> thenCombineAsync(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn);public <U,V> CompletionStage<V> thenCombineAsync(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn,Executor executor);
  • thenCompose()

thenCompose 方法允许你对两个CompletableFuture任务进行流水线操作,当第一个异步任务操作完成时,会将其结果作为参数传递给第二个任务。

public <U> CompletableFuture<U> thenCompose(Function<? super T, ? extends CompletionStage<U>> fn) {return uniComposeStage(null, fn);
}public <U> CompletableFuture<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn) {return uniComposeStage(defaultExecutor(), fn);
}public <U> CompletableFuture<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn,Executor executor) {return uniComposeStage(screenExecutor(executor), fn);
}
3.4.2 功能演示
  • 代码演示 thenCombine()
        CompletableFuture<String> helloAsync = CompletableFuture.supplyAsync(() -> {System.out.println("hello 执行线程:" + Thread.currentThread().getName());return "hello";});CompletableFuture<String> worldAsync = CompletableFuture.supplyAsync(() -> {System.out.println("world 执行线程:" + Thread.currentThread().getName());return "world";});CompletableFuture<String> thenCombine = worldAsync.thenCombine(helloAsync, (hello, world) -> {System.out.println("result 执行线程:" + Thread.currentThread().getName());return (hello + "," + world).toUpperCase();});System.out.println("获取结果 执行线程:" + Thread.currentThread().getName());System.out.println("thenCombine 两个异步任务合并结果:" + thenCombine.join());
  • 输出结果
hello 执行线程:ForkJoinPool.commonPool-worker-1
world 执行线程:ForkJoinPool.commonPool-worker-1
result 执行线程:main
获取结果 执行线程:main
thenCombine 两个异步任务合并结果:WORLD,HELLO
  • 代码演示 thenCompose()
        CompletableFuture<String> thenCompose = CompletableFuture.supplyAsync(() -> {System.out.println("hello 执行线程:" + Thread.currentThread().getName());return "hello";}).thenCompose((hello -> {System.out.println("thenCompose 执行线程:" + Thread.currentThread().getName());return CompletableFuture.supplyAsync((hello + "world")::toUpperCase);}));System.out.println("获取结果 执行线程:" + Thread.currentThread().getName());System.out.println("thenCompose 两个异步任务流水线执行结果:" + thenCompose.join());
  • 输出结果
hello 执行线程:ForkJoinPool.commonPool-worker-1
thenCompose 执行线程:main
获取结果 执行线程:main
thenCompose 两个异步任务流水线执行结果:HELLOWORLD

3.5 并行运行多个 CompletableFuture

3.5.1 功能介绍

CompletableFutureallOf()这个静态方法来并行运行多个 CompletableFuture;

在实际的工作中,会有很多这样的场景,比如:查询订单明细,要订单信息,订单明细,商品,sku等。四个不相关的任务可以同时独立操作查询。

public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs)
3.5.2 功能演示
  • 代码演示
long startTime = System.currentTimeMillis();
System.out.println("startTime = " + startTime);
CompletableFuture<String> orderFuture = CompletableFuture.supplyAsync(() -> {try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("orderFuture-查询订单信息完成!");return "orderFuture-查询订单信息完成!";
});
CompletableFuture<String> orderDetailsFuture = CompletableFuture.supplyAsync(() -> {try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("orderDetailsFuture-查询订单明细信息完成!");return "orderDetailsFuture-查询订单明细信息完成!";
});
CompletableFuture<String> productFuture = CompletableFuture.supplyAsync(() -> {try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("productFuture-查询商品信息完成!");return "productFuture-查询商品信息完成!";
});
CompletableFuture<String> skuFuture = CompletableFuture.supplyAsync(() -> {try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("skuFuture-查询sku信息完成!");return "skuFuture-查询sku信息完成!";
});
// 组合
CompletableFuture<Void> resultFuture = CompletableFuture.allOf(orderFuture, orderDetailsFuture, productFuture, skuFuture);
resultFuture.join();
long endTime = System.currentTimeMillis();
System.out.println("endTime = " + endTime);
System.out.println("任务总耗时:" + (endTime - startTime));
// 组合结果
System.out.println("最终结果为:" + orderFuture.join() + "\n" + orderDetailsFuture.join() + "\n" + productFuture.join() + "\n" + skuFuture.join());
  • 输出结果
startTime = 1721030494355
skuFuture-查询sku信息完成!
productFuture-查询商品信息完成!
orderDetailsFuture-查询订单明细信息完成!
orderFuture-查询订单信息完成!
endTime = 1721030496369
任务总耗时:2014
最终结果为:orderFuture-查询订单信息完成!
orderDetailsFuture-查询订单明细信息完成!
productFuture-查询商品信息完成!
skuFuture-查询sku信息完成!

四、总结

本文主要讲了CompletableFuture的一些功能的用法和集中常用的API。CompletableFuture提供了灵活的异步任务处理的方式,使用起来也是比较的方便。这里要注意的是,**在使用CompletableFuture的时候一定要使用自定义的线程池。**如果要想深入的理解CompletableFuture,还要自己多用心的去学习。希望文章内容能对大家理解CompletableFuture有一定的帮助,有问题或好的提议也请大家指出来,大家一起学习,一起进步。

纸上得来终觉浅,绝知此事要躬行。

参考

  • https://blog.csdn.net/sermonlizhi/article/details/123356877?spm=1001.2014.3001.5506
  • https://blog.csdn.net/leilei1366615/article/details/119855928?spm=1001.2014.3001.5506
  • https://tech.meituan.com/2022/05/12/principles-and-practices-of-completablefuture.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/46539.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

金蝶云星空与金蝶云星空对接集成付款单查询打通[标准][付款单新增]-v1

金蝶云星空与金蝶云星空对接集成付款单查询打通[标准][付款单新增]-v1 对接源平台:金蝶云星空 金蝶K/3Cloud在总结百万家客户管理最佳实践的基础上&#xff0c;提供了标准的管理模式&#xff1b;通过标准的业务架构&#xff1a;多会计准则、多币别、多地点、多组织、多税制应用…

Gstreamer学习3.1------使用appsrc灌颜色信号数据

这个视频内容讲解的离散余弦变换&#xff0c;讲的很好&#xff0c; 离散余弦变换可视化讲解_哔哩哔哩_bilibili 其中讲到&#xff0c;把颜色变化转换为曲线的处理&#xff0c; 在前面的学习中&#xff0c;我们知道了可以向appsrc来灌数据来进行显示 Gstreamer学习3----灌数据…

yolo格式数据集之野生动物类4种数据集已划分好|可以直接使用|yolov5|v6|v7|v8|v9|v10通用

本数据为野生动物类检测数据集&#xff0c;数据集数量如下&#xff1a; 总共有:1504张 训练集&#xff1a;1203张 验证集&#xff1a;150张 类别数量&#xff1a;4 测试集&#xff1a;151 类别名&#xff1a; [‘buffalo’, ‘elephant’, ‘rhino’, ‘zebra’] 占用空间&…

自动驾驶-端到端分割任务

上采样 bed of nails interpolation transposed convolutions 1. 上采样 (Upsampling) 上采样是一种技术&#xff0c;用于增加数据集中的样本数量或是提高信号的分辨率。在图像处理中&#xff0c;上采样通常指的是增加图像的像素数量&#xff0c;从而使图像变得更大。这可…

如何用STM32实现modbus-RTU?

Modbus RTU是一种广泛应用于工业自动化领域的通信协议,基于主从架构,通过串行通信进行数据传输。本文将详细介绍Modbus RTU协议的基本原理,并提供在STM32微控制器上实现Modbus RTU通信的完整代码示例。 1. Modbus RTU协议概述 Modbus RTU的定义和特点 Modbus RTU(Remote Te…

哥德巴赫猜想c++

方法一 #include<bits/stdc.h> using namespace std; //定义函数&#xff0c;判断素数 bool sushu(int n){bool rtrue;//先假设是素数&#xff0c;即真//循环因子范围&#xff0c;找到一个因子就不是素数for(int i2;i<sqrt(n);i){//判断2~n的根号是否素数if(n%i0){//…

【Linux】从零开始认识多线程 --- 线程控制

在这个浮躁的时代 只有自律的人才能脱颖而出 -- 《觉醒年代》 从零开始认识多线程 --- 线程控制 1 知识回顾2 线程控制2.1 线程创建2.2 线程等待2.3 线程终止 3 测试运行3.1 小试牛刀 --- 创建线程3.2 探幽析微 --- 理解线程参数3.3 小有心得 --- 探索线程返回3.4 求索无厌 …

数据结构_顺序表专题

何为数据结构&#xff1f; 咱今天也来说道说道...... 数据结构介绍 准确概念 数据结构就是计算机存储、组织数据的方式 概念分析 从上句分析&#xff0c;数据结构是一种方式。一种管理数据的方式。为了做什么&#xff1f;为的就是计算机存储数据&#xff0c;组织数据。 …

元器件基础学习笔记——磁珠

一、磁珠的作用及构造 1.1 磁珠的作用 磁珠是一种用于抑制高频噪声的被动电子组件&#xff0c;通常由铁氧体材料制成&#xff0c;这种材料具有高电阻率和高磁导率&#xff0c;使其能够在高频下有效地将干扰信号以热能的形式消耗掉。在电路设计中&#xff0c;磁珠被广泛用于信号…

LeetCode 441, 57, 79

目录 441. 排列硬币题目链接标签思路代码 57. 插入区间题目链接标签思路两个区间的情况对每个区间的处理最终的处理 代码 79. 单词搜索题目链接标签原理思路代码 优化思路代码 441. 排列硬币 题目链接 441. 排列硬币 标签 数学 二分查找 思路 由于本题所返回的 答案在区间…

3d复制的模型怎么渲染不出来?----模大狮模型网

在展览3D模型设计领域&#xff0c;技术的进步和创新使得模型的复杂性和精细度有了显著提升。然而&#xff0c;有时设计师们在尝试渲染复杂的3D复制模型时&#xff0c;却面临着无法正确呈现的问题。模大狮将探讨这一现象的可能原因&#xff0c;并提供相应的解决方案和建议&#…

知识图谱和 LLM:利用Neo4j驾驭大型语言模型(探索真实用例)

这是关于 Neo4j 的 NaLLM 项目的一篇博客文章。这个项目是为了探索、开发和展示这些 LLM 与 Neo4j 结合的实际用途。 2023 年,ChatGPT 等大型语言模型 (LLM) 因其理解和生成类似人类的文本的能力而风靡全球。它们能够适应不同的对话环境、回答各种主题的问题,甚至模拟创意写…

3d导入模型后墙体变成黑色?---模大狮模型网

在展览3D模型设计领域&#xff0c;技术和设计的融合通常是创意和实现之间的桥梁。然而&#xff0c;有时设计师们会遇到一些技术上的挑战&#xff0c;如导入3D模型后&#xff0c;墙体却突然变成了黑色。这种问题不仅影响了设计的视觉效果&#xff0c;也反映了技术应用中的一些复…

【UE5.3】笔记10-时间轴的使用

时间轴 右键--Add Timeline(在最下面) --> 双击进入时间轴的编辑界面&#xff1a; 左上角可以添加不同类型的轨道&#xff0c;可以自定义轨道的长度&#xff0c;单位秒&#xff0c;一次可以添加多个 可以通过右键添加关键帧&#xff0c;快捷键&#xff1a;shift鼠标左键按…

C++基础语法:链表和数据结构

前言 "打牢基础,万事不愁" .C的基础语法的学习 引入 链表是最基础的数据集合,对标数组.数组是固定长度,随机访问,链表是非固定长度,不能随机访问.数组查找快,插入慢;链表是插入快,查找慢. 前面推导过"数据结构算法数据集合".想建立一个数据集合,就要设计数…

鸿蒙 画布来了 我不允许你不会

前言: 作者:徐庆 团队:坚果派 公众号:“大前端之旅” 润开鸿生态技术专家,华为HDE,CSDN博客专家,CSDN超级个体,CSDN特邀嘉宾,InfoQ签约作者,OpenHarmony布道师,电子发烧友专家博客,51CTO博客专家,擅长HarmonyOS/OpenHarmony应用开发、熟悉服务卡片开发。欢迎合作…

WPF+MvvmLight 项目入门完整教程(一)

WPF+MvvmLight入门完整教程一 创建项目MvvmLight框架安装完善整个项目的目录结构创建自定义的字体资源下载更新和使用字体资源创建项目 打开VS2022,点击创建新项目,选择**WPF应用(.NET Framework)** 创建一个名称为 CommonProject_DeskTop 的项目,如下图所示:MvvmLight框架…

OpenCV开发笔记(七十八):在ubuntu上搭建opencv+python开发环境以及匹配识别Demo

若该文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/140435870 长沙红胖子Qt&#xff08;长沙创微智科&#xff09;博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV…

【Nuxt3】vue3+tailwindcss+vuetify引入自定义字体样式

一、目的 在项目中引入自定义的字体样式&#xff08;全局页面都可使用&#xff09; 二、步骤 1、下载好字体 字体的后缀可以是ttf、otf、woff、eot或者svg&#xff08;推荐前三种&#xff09; 以抖音字体为例下载好放在静态文件夹&#xff08;font&#xff09;下 案例字…

【论文阅读】《Visual Prompt Tuning》

Abstract. 目前调整预训练模型的工作方式包括更新所有骨干参数&#xff0c;即全面微调。本文介绍了视觉提示调整&#xff08;VPT&#xff09;&#xff0c;作为大规模视觉变换器模型全面微调的高效替代方案。VPT 从高效调整大型语言模型的最新进展中汲取灵感&#xff0c;只在输…