jdk8新特性-CompletableFuture的来源、源码解析及实际应用场景

文章目录

  • 1.介绍
  • 2.源码解析
    • CompletableFuture类定义
      • Future接口
        • Future的get方法
      • CompletableStage接口
    • CompletableFuture的工作流
      • 初始化completed
      • 初始化主动complete
      • 异常complete
  • 3.核心属性
    • Completion stack
  • 4.核心静态方法
  • 5.常用方法
    • (1)是否获取返回值方法
      • AllOf
      • AnyOf
    • (2)获得结果和触发计算类型的方法
    • (3)对计算结果进行处理类型的方法
      • thenApply
      • handle
      • thenApply 与 thenCompose 对比
      • whenComplete
      • exceptionally、whenComplete、handle 对比
    • (4) 对计算结果进行消费类型的方法
      • thenAccept
      • thenRun
      • thenRun、thenAccept、thenApply 对比
      • thenRun 与 thenRunAsync 对比
      • thenAccept 与 thenAcceptAsync、thenApply 与 thenApplyAsync 同理
    • (5) 对计算速度进行选用类型的方法
      • applyToEither
      • applyToEither、acceptEither、runAfterEither 对比
    • (6) 对计算结果进行合并类型的方法
      • thenCombine
      • thenCombine、thenAcceptBoth、runAfterBoth 对比
  • 6.性能分析
    • 测试任务数等于 CompletableFuture 线程池的最大线程数:
      • 第一种写法:
      • 第二种写法(用Stream简化代码,结果还是一样,主要是简化代码):
    • 测试任务数超过 CompletableFuture线程池的最大线程数:
    • CompletableFuture 配合自定义线程池使用

1.介绍

​ Java从JDK5开始引入了Future,用来描述一个异步计算的结果,平时开发过程中 RunableFutureThreadExecutorServiceCallable 这些和多线程相关的类相互配合对开发人员来说已经非常熟悉和了解。使用起来也得心应手。但是这些类组合在一起解决多线程的问题的同时也逐渐暴露出难以满足的开发需求。

​ 在JDK8之前,我们使用的Java多线程编程,主要是 Thread+Runnable 来完成,但是这种方式有个最大的弊端就是没有返回值。如果想要返回值怎么办呢,大多数人就会想到 Future+Callable 的方式来获取到返回值。Future是一个可以代表异步计算结果的对象,并且Future提供了一些方法来让调用者控制任务,比如可以取消任务的执行(当然可能取消会失败),或者设置超时时间来取得我们的任务的运行结果。如下:

FutureTask<String> task = new FutureTask((Callable<String>) ()->{
    TimeUnit.SECONDS.sleep(2);
    return UUID.randomUUID().toString();
});

    new Thread(task).start();
    String s = task.get();
    System.out.println(s);

虽然Future以及相关使用方法提供了异步执行任务的能力,但是对于结果的获取却是很不方便,只能通过阻塞或者轮询的方式得到任务的结果。

​ 从上面的示例来看轮询的方式势必会耗费无谓的CPU资源,而且也不能及时地得到计算结果.所以要实现真正的异步,上述这样是完全不够的。

  • Runnable+Thread虽然提供了多线程的能力但是没有返回值。
  • Future + Callable的方法提供多线程和返回值的能力但是在获取返回值的时候会阻塞主线程。

​ 所以,从JDK1.8开始,CompletableFuture就为我们提供了异步函数式编程,CompletableFuture提供了非常强大的Future的扩展功能,CompletableFuture继承了FutureTask的同步任务的特点,同时新增了异步调用的特点,说简单点就是它可以让代码一起运行,不需要一个个运行,CompletableFuture可以帮助我们简化异步编程的复杂性,提供了函数式编程的能力,可以通过回调的方式处理计算结果,并且提供了转换和组合CompletableFuture的方法。

2.源码解析

CompletableFuture类定义

public class CompletableFuture<T>
extends Object
implements java.util.concurrent.Future<T>, java.util.concurrent.CompletionStage<T>

CompletableFuture实现了FutureCompletionStage两个接口。

jdk8新特性-CompletableFuture的来源、源码解析及实际应用场景_第1张图片

Future接口

​ 其中Future大家应该都很熟悉了,在异步应用中也很常见,这里简单的回顾下普通模式和Future模式的区别:

jdk8新特性-CompletableFuture的来源、源码解析及实际应用场景_第2张图片

​ 可以看到当工作线程的结果我们并不急着需要的话,可以交给Future,然后主线程可以去做一些别的事情,当需要工作线程结果的时候,使用get()来尝试获取即可。注意get()方法是阻塞的,这也是Future常被吐槽的地方,另外Future无法表达任务间的依赖关系也是它的一个局限。

Future的get方法

CompletableFuture实现了Future的所有接口,包括两个get方法,一个是不带参数的get方法,一个是可以设置等待时间的get方法,首先来看一下CompletableFuture中不带参数的get方法的具体实现:

    public T get() throws InterruptedException, ExecutionException {
        Object r;
        return reportGet((r = result) == null ? waitingGet(true) : r);
    }

​ result字段代表任务的执行结果,所以首先判断是否为null,为null则表示任务还没有执行结束,那么就会调用waitingGet方法来等待任务执行完成,如果result不为null,那么说明任务已经成功执行结束了,那么就调用reportGet来返回结果,下面先来看一下waitingGet方法的具体实现细节:

/**
     * Returns raw result after waiting, or null if interruptible and
     * interrupted.
     */
    private Object waitingGet(boolean interruptible) {
        Signaller q = null;
        boolean queued = false;
        int spins = -1;
        Object r;
        while ((r = result) == null) {
            if (spins < 0)
                spins = (Runtime.getRuntime().availableProcessors() > 1) ?
                    1 << 8 : 0; // Use brief spin-wait on multiprocessors
            else if (spins > 0) {
                if (ThreadLocalRandom.nextSecondarySeed() >= 0)
                    --spins;
            }
            else if (q == null)
                q = new Signaller(interruptible, 0L, 0L);
            else if (!queued)
                queued = tryPushStack(q);
            else if (interruptible && q.interruptControl < 0) {
                q.thread = null;
                cleanStack();
                return null;
            }
            else if (q.thread != null && result == null) {
                try {
                    ForkJoinPool.managedBlock(q);
                } catch (InterruptedException ie) {
                    q.interruptControl = -1;
                }
            }
        }
        if (q != null) {
            q.thread = null;
            if (q.interruptControl < 0) {
                if (interruptible)
                    r = null; // report interruption
                else
                    Thread.currentThread().interrupt();
            }
        }
        postComplete();
        return r;
    }

​ 这个方法的实现时比较复杂的,方法中有几个地方需要特别注意,下面先来看一下spins是做什么的,根据注释,可以知道spins是用来在多核心环境下的自旋操作的,所谓自旋就是不断循环等待判断,从代码可以看出在多核心环境下,spins会被初始化为1 << 8,然后在自旋的过程中如果发现spins大于0,那么就通过一个关键方法ThreadLocalRandom.nextSecondarySeed()来进行spins的更新操作,如果ThreadLocalRandom.nextSecondarySeed()返回的结果大于0,那么spins就减1,否则不更新spins。 ThreadLocalRandom.nextSecondarySeed()方法其实是一个类似于并发环境下的random,是线程安全的。

​ 接下来还需要注意的一个点是Signaller,从Signaller的实现上可以发现,Signaller实现了ForkJoinPool.ManagedBlocker,下面是ForkJoinPool.ManagedBlocker的接口定义:

 public static interface ManagedBlocker {
        /**
         * Possibly blocks the current thread, for example waiting for
         * a lock or condition.
         *
         * @return {@code true} if no additional blocking is necessary
         * (i.e., if isReleasable would return true)
         * @throws InterruptedException if interrupted while waiting
         * (the method is not required to do so, but is allowed to)
         */
        boolean block() throws InterruptedException;

        /**
         * Returns {@code true} if blocking is unnecessary.
         * @return {@code true} if blocking is unnecessary
         */
        boolean isReleasable();
    }

ForkJoinPool.ManagedBlocker的目的是为了保证ForkJoinPool的并行性,具体分析还需要更为深入的学习Fork/Join框架。继续回到waitingGet方法中,在自旋过程中会调用ForkJoinPool.managedBlock(ForkJoinPool.ManagedBlocker)来进行阻塞工作,实际的效果就是让线程等任务执行完成,CompletableFuture中与Fork/Join的交叉部分内容不再本文的描述范围,日后再进行分析总结。总得看起来,waitingGet实现的功能就是等待任务执行完成,执行完成返回结果并做一些收尾工作。

​ 现在来看reportGet方法的实现细节,在判断任务执行完成之后,get方法会调用reportGet方法来获取结果:

    /**
     * Reports result using Future.get conventions.
     */
    private static <T> T reportGet(Object r)
        throws InterruptedException, ExecutionException {
        if (r == null) // by convention below, null means interrupted
            throw new InterruptedException();
        if (r instanceof AltResult) {
            Throwable x, cause;
            if ((x = ((AltResult)r).ex) == null)
                return null;
            if (x instanceof CancellationException)
                throw (CancellationException)x;
            if ((x instanceof CompletionException) &&
                (cause = x.getCause()) != null)
                x = cause;
            throw new ExecutionException(x);
        }
        @SuppressWarnings("unchecked") T t = (T) r;
        return t;
    }

​ 分析完了不带参数的get方法(阻塞等待)之后,现在来分析一下带超时参数的get方法的具体实现细节:

  public T get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException {
        Object r;
        long nanos = unit.toNanos(timeout);
        return reportGet((r = result) == null ? timedGet(nanos) : r);
    }

​ 和不带参数的get方法一样,还是会判断任务是否已经执行完成了,如果完成了会调用reportGet方法来返回最终的执行结果(或者抛出异常),否则,会调用timedGet来进行超时等待,timedGet会等待一段时间,然后抛出超时异常(或者执行结束返回正常结果),下面是timedGet方法的具体细节:

 private Object timedGet(long nanos) throws TimeoutException {
        if (Thread.interrupted())
            return null;
        if (nanos <= 0L)
            throw new TimeoutException();
        long d = System.nanoTime() + nanos;
        Signaller q = new Signaller(true, nanos, d == 0L ? 1L : d); // avoid 0
        boolean queued = false;
        Object r;
        // We intentionally don't spin here (as waitingGet does) because
        // the call to nanoTime() above acts much like a spin.
        while ((r = result) == null) {
            if (!queued)
                queued = tryPushStack(q);
            else if (q.interruptControl < 0 || q.nanos <= 0L) {
                q.thread = null;
                cleanStack();
                if (q.interruptControl < 0)
                    return null;
                throw new TimeoutException();
            }
            else if (q.thread != null && result == null) {
                try {
                    ForkJoinPool.managedBlock(q);
                } catch (InterruptedException ie) {
                    q.interruptControl = -1;
                }
            }
        }
        if (q.interruptControl < 0)
            r = null;
        q.thread = null;
        postComplete();
        return r;
    }

​ 在timedGet中不再使用spins来进行自旋,因为现在可以确定需要等待多少时间了。timedGet的逻辑和waitingGet的逻辑类似,毕竟都是在等待任务的执行结果。

​ 除了两个get方法之前,CompletableFuture还提供了一个方法getNow,代表需要立刻返回不进行阻塞等待,下面是getNow的实现细节:

   public T getNow(T valueIfAbsent) {
        Object r;
        return ((r = result) == null) ? valueIfAbsent : reportJoin(r);
    }

​ getNow很简单,判断result是否为null,如果不为null则直接返回,否则返回参数中传递的默认值。

CompletableStage接口

CompletableStage用来表示异步过程中的一个阶段,它可以在另一个CompletableStage完成时做一些操作或计算,此接口中定义了一些基本的行为,通过这些行为组合可以简洁的描述非常复杂的任务。

常用的几个方法:

  • thenApply 将上一个stage的结果转化成新的类型或值
  • thenAccept 将上一个stage的结果进行消耗,无返回值
  • thenRun 在上一个stage有结果后,执行一段新的操作
  • thenCombine 结合两个CompletableStage的结果,转化成新的类型或值
  • thenCompose 返回一个新的CompletableStage,并将上一个stage的结果作为新的stage的supplier
  • exceptionally 当运算过程中遇到异常时的一个补偿处理
  • handle 统一了对正常结果和异常结果的处理

大部分方法都有以Async结尾的方法,表示异步执行,后面会提到。更多信息可以参考jdk文档。

CompletableFuture的工作流

CompletableFuture初始化时可以处于completed和incompleted两种状态,先看两个最简单的例子。

初始化completed

// base直接初始化成一个已完成的CompletableFuture,完成值是"completed"
CompletableFuture<String> base = CompletableFuture.completedFuture("completed");
log.info(base.get());

输出:

[INFO ] [2019-07-15 10:05:13] [main] completed

​ 这里base对象是一个已完成的CompletableFuture,所以get()直接返回了"completed"。当然如果初始化时用了未完成的CompletableFuture,那么get()方法是会阻塞等待它完成,这个就成了Future模式,毕竟get()方法是在Future接口中定义的。

初始化主动complete

​ 我们也可以在之后的代码中,或是其他线程中将它“完成”:

// 这是一个未完成的CompletableFuture
CompletableFuture<String> base = new CompletableFuture<>();
log.info("start another thread to complete it");
new Thread(
    () -> {
        log.info("will complete in 1 sec");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        base.complete("completed");
    })
    .start();
log.info(base.get());

输出:

[INFO ] [2019-07-15 14:32:26] [main] start another thread to complete it
[INFO ] [2019-07-15 14:32:26] [Thread-0] will complete in 1 sec
[INFO ] [2019-07-15 14:32:27] [main] completed

​ 这个例子中主线程在调用get()方法时阻塞,Thread-0线程在sleep 1秒后调用complete()方法将base完成,主线程get()返回得到完成值completed。

异常complete

CompletableFuture<String> base = new CompletableFuture<>();
base.completeExceptionally(new RuntimeException(("runtime error")));
log.info(base.get());

输出:

Exception in thread "main" java.util.concurrent.ExecutionException: java.lang.RuntimeException: runtime error
  at java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:357)
  at java.util.concurrent.CompletableFuture.get(CompletableFuture.java:1895)
  at com.aliyun.completable.Main.main(Main.java:33)
Caused by: java.lang.RuntimeException: runtime error
  at com.aliyun.completable.Main.main(Main.java:32)

​ 在complete时发生异常,在base调用get()方法时抛出ExecutionException

​ 我们可以得出最基本的一个流程,CompletableFuture是靠complete作为一个初始力来驱动的,虽然这不是它的全部,但至少得complete它才会去继续执行后面依赖它的一系列处理。

3.核心属性

Completion stack

​ CompletableFuture两个核心的关键属性result和stack,关于这两个属性也有核心的注释,result可能是返回的结果集,也可能是包装的AltResult,stack这个数据暴露出了CompletableFuture的整体的结构是一个栈。

volatile Object result;       // Either the result or boxed AltResult
volatile Completion stack;    // Top of Treiber stack of dependent actions

jdk8新特性-CompletableFuture的来源、源码解析及实际应用场景_第3张图片

Completion的源码:

abstract static class Completion extends ForkJoinTask<Void>
        implements Runnable, AsynchronousCompletionTask {
        volatile Completion next;      
        abstract CompletableFuture<?> tryFire(int mode);
        abstract boolean isLive();

        public final void run()                { tryFire(ASYNC); }
        public final boolean exec()            { tryFire(ASYNC); return true; }
        public final Void getRawResult()       { return null; }
        public final void setRawResult(Void v) {}
    }

Completion是一个抽象类,分别实现了RunnableAsynchronousCompletionTask接口,继承了ForkJoinPoolTask类,而ForJoinPoolTask抽象类又实现了Future接口,因此Completion实际上就是一个Future。可以看到Completion的抽象方法和成员方法的实现逻辑都短短一行或者没有,可以猜到这些方法的实现都是在其子类中。其实现类包括了UniCompletionBiCompletionUniAcceptBiAccept等。

​ 结合我们上面看到的CompletableFuture的stack属性,整好能验证CompletableFuture是一个链表的一个数据结构,Completion中的next保存了栈中下一个元素的引用,而CompletableFuture中的stack永远指向栈顶。

4.核心静态方法

  • runAsync(Runnable runnable)
  • runAsync(Runnable runnable, Executor executor)
  • supplyAsync(Supplier supplier)
  • supplyAsync(Supplier supplier, Executor executor)
//runAsync 执行的任务无返回值,
public static CompletableFuture<Void> runAsync(Runnable runnable)
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor) 
//supplyAsync 执行的任务有返回值
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)
//以上传入 Executor 可以使用我们指定的线程池,不传则使用默认的 ForkJoinPool.commonPool()线程池

CompletableFuture 源码:

public class CompletableFuture<T> implements Future<T>, CompletionStage<T> {
	// 默认线程池 —— ForkJoinPool.commonPool() 除非它不支持并行。
	private static final Executor asyncPool = useCommonPool ? ForkJoinPool.commonPool() : new ThreadPerTaskExecutor();
	

	// 如果 ForkJoinPool.commonPool() 不支持并行性,则回退
	static final class ThreadPerTaskExecutor implements Executor {
	    public void execute(Runnable r) { 
	    	new Thread(r).start(); 
	    }
	}
	// 返回一个新的 CompletableFuture,它在运行给定操作后由 ForkJoinPool.commonPool() 中运行的任务异步完成。
	public static CompletableFuture<Void> runAsync(Runnable runnable) {
	    return asyncRunStage(asyncPool, runnable);
	}
	
	// 返回一个新的 CompletableFuture,它由在 ForkJoinPool.commonPool() 中运行的任务异步完成,调用者可以获取返回值。
	public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) {
	    return asyncSupplyStage(asyncPool, supplier);
	}

}

测试 1:

@Slf4j(topic = "c.TestsSupplyAsync")
public class TestsSupplyAsync {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService pool = Executors.newFixedThreadPool(3);
        CompletableFuture<String> supplyAsync = CompletableFuture.supplyAsync(() -> {
        	log.debug("线程名:{}", Thread.currentThread().getName());
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "hello supplyAsync";
        }, pool);

        log.debug("{}", supplyAsync.get());
        // 也可以使用join,使用join可以不用声明异常

//      log.debug("{}", supplyAsync.join());
        pool.shutdown();
    }
}
17:52:23.694 c.TestsSupplyAsync [pool-1-thread-1] - 线程名:pool-1-thread-1
17:52:24.706 c.TestsSupplyAsync [main] - hello supplyAsync

测试 2:

需求:

同时搜索出同款产品在各大平台的售价
输出格式:List< String>
《Java》in JD price is ***
《Java》in DangDang price is ***
《Java》in Taobao price is ***

public class TestCompletableFuture {

    static List<Mall> list = Arrays.asList(
            new Mall("JD"),
            new Mall("DangDang"),
            new Mall("TaoBao")
    );
    
    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        List<String> price = getPrice(list, "java");
        price.forEach(System.out::println);
        long end = System.currentTimeMillis();
        System.out.println("耗时:" + (end - start) + "毫秒");
    }
    
    // List -> List> -> List
    public static List<String> getPrice(List<Mall> mallList, String bookName) {
        return mallList.stream()
                .map(mall -> CompletableFuture.supplyAsync(() ->
                        String.format(bookName + " in %s price is %.2f",
                                mall.getMallName(),
                                mall.getBookPrice(bookName))))
                .collect(Collectors.toList())
                .stream()
                .map(CompletableFuture::join)
                .collect(Collectors.toList());
    }

}

class Mall {

    @Getter
    private final String mallName;
    
    public Mall(String mallName) {
        this.mallName = mallName;
    }
    
    public double getBookPrice(String bookName){
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 随机产生价格
        return ThreadLocalRandom.current().nextDouble() * 2 + bookName.charAt(0);
    }

}
java in JD price is 106.31
java in DangDang price is 106.35
java in TaoBao price is 107.81
耗时:1125毫秒

5.常用方法

(1)是否获取返回值方法

AllOf

​ 所有任务都执行完成后,才执行 allOf返回的CompletableFuture。如果任意一个任务异常,allOf的CompletableFuture,执行get方法,会抛出异常。

CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(()->{ System.out.println("第一个异步任务执行完了"); });
CompletableFuture<Void> runAsync = CompletableFuture.runAsync(() -> { System.out.println("第二个异步任务执行完了"); }); 
CompletableFuture.allOf(completableFuture,runAsync).whenComplete((a,b) -> { 
    System.out.println("finish");
});

AnyOf

​ 任意一个任务执行完,就执行anyOf返回的CompletableFuture。如果执行的任务异常,anyOf的CompletableFuture,执行get方法,会抛出异常。

CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(()->{ 
    try { 
        Thread.sleep(2000);
    } catch (InterruptedException e) { 
        e.printStackTrace();
    } 
    System.out.println("第一个异步任务执行完了"); 
}); 
CompletableFuture<Void> runAsync = CompletableFuture.runAsync(() -> { 
    System.out.println("第二个异步任务执行完了"); 
}); 
CompletableFuture.anyOf(completableFuture,runAsync).whenComplete((a,b) -> { 
    System.out.println("finish"); 
});

(2)获得结果和触发计算类型的方法

获得结果类型的方法

  • get()
  • get(long timeout, TimeUnit unit)
  • join()
  • getNow(T valueIfAbsent)
// 等待任务完成,并返回结果
public T get() throws InterruptedException, ExecutionException
// 带超时的等待,并返回结果,超时则抛出异常
public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException
// 等待任务完成,并返回结果,不用声明异常 (编译时不会检查异常)
// 出现异常时,抛出 CompletionException 异常
public T join()
// 如果任务完成则返回结果值(或抛出任何遇到的异常),否则返回给定的参数值 valueIfAbsent
// 不会阻塞
public T getNow(T valueIfAbsent)

测试 getNow()

public static void main(String[] args) {
        CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "测试getNow方法";
        });
        System.out.println("结果:" + completableFuture.getNow("这是任务没完成返回的指定值"));
结果:这是任务没完成返回的指定值

主动触发计算的方法

// 如果尚未完成,则将 get() 和相关方法返回的值设置为给定值
// 表示是否打断get()、join()等方法,立即返回指定值 value
public boolean complete(T value)

测试 complete():

public static void main(String[] args) throws InterruptedException, ExecutionException {
        CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
            try {
                // 执行 2s
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "测试getNow方法";
        });

    // 等 1s
    TimeUnit.SECONDS.sleep(1);
    // 是否打断get()等方法
    boolean flag = completableFuture.complete("这是任务没完成返回的指定值");
   String value = completableFuture.get();
    System.out.println("是否打断:" + flag + ", 结果:" + value);
}
是否打断:true, 结果:这是任务没完成返回的指定值

(3)对计算结果进行处理类型的方法

thenApply

  • 计算结果存在依赖关系,多个线程之间是串行化的
  • 当前阶段出现异常,将不会执行后面阶段
  • 返回一个新的 CompletionStage,当此阶段正常完成时,将使用此阶段的结果作为下一个阶段的参数
public <U> CompletionStage<U> thenApply(Function<? super T,? extends U> fn);

测试正常情况:

@Slf4j(topic = "c.TestThenApply")
public class TestThenApply {
    public static void main(String[] args) {
        ExecutorService pool = Executors.newFixedThreadPool(3);
        CompletableFuture.supplyAsync(() -> {
            log.debug("第一阶段");
            return 1;
        }, pool).thenApply((v) -> {
            log.debug("第二阶段");
            return v + 2;
        }).thenApply((v) -> {
            log.debug("第三阶段");
            return v + 3;
        }).whenComplete((v, e) -> {
            log.debug("结果:" + v);
        }).exceptionally(e -> {
            log.debug("出现异常:" + e.getMessage());
            return null;
        });

    pool.shutdown();
}

}
22:45:39.496 c.TestThenApply [pool-1-thread-1] - 第一阶段
22:45:39.499 c.TestThenApply [pool-1-thread-1] - 第二阶段
22:45:39.499 c.TestThenApply [pool-1-thread-1] - 第三阶段
22:45:39.500 c.TestThenApply [pool-1-thread-1] - 结果:6

测试异常情况:

@Slf4j(topic = "c.TestThenApply")
public class TestThenApply {
    public static void main(String[] args) {
        ExecutorService pool = Executors.newFixedThreadPool(3);
        CompletableFuture.supplyAsync(() -> {
            log.debug("第一阶段");
            return 1;
        }, pool).thenApply((v) -> {
            int i = 10 / 0;
            log.debug("第二阶段");
            return v + 2;
        }).thenApply((v) -> {
            log.debug("第三阶段");
            return v + 3;
        }).whenComplete((v, e) -> {
            log.debug("结果:" + v);
        }).exceptionally(e -> {
            log.debug("出现异常:" + e.getMessage());
            return null;
        });

    pool.shutdown();
}

}
22:46:05.153 c.TestThenApply [pool-1-thread-1] - 第一阶段
22:46:05.158 c.TestThenApply [pool-1-thread-1] - 结果:null
22:46:05.158 c.TestThenApply [pool-1-thread-1] - 出现异常:java.lang.ArithmeticException: / by zero

handle

  • 计算结果存在依赖关系,多个线程之间是串行化的
  • 当前阶段出现异常,将会执行后面阶段,可以根据异常做进一步处理
  • 返回一个新的 CompletionStage,当此阶段正常或异常完成时,将使用此阶段的结果作为下一个阶段的参数
public <U> CompletionStage<U> handle(BiFunction<? super T, Throwable, ? extends U> fn);

测试正常情况:

@Slf4j(topic = "c.TestHandle")
public class TestHandle {
    public static void main(String[] args) {
        ExecutorService pool = Executors.newFixedThreadPool(3);
        CompletableFuture.supplyAsync(() -> {
            log.debug("第一阶段");
            return 1;
        }, pool).handle((v, e) -> {
            log.debug("第二阶段");
            return v + 2;
        }).handle((v, e) -> {
            log.debug("第三阶段");
            if (e != null) {
                log.debug("捕获上一阶段的异常:" + e.getMessage());
            }
            return v + 3;
        }).whenComplete((v, e) -> {
            log.debug("结果:" + v);
        }).exceptionally(e -> {
            log.debug("出现异常:" + e.getMessage());
            return null;
        });

    pool.shutdown();
}

}
22:39:52.103 c.TestHandle [pool-1-thread-1] - 第一阶段
22:39:52.107 c.TestHandle [pool-1-thread-1] - 第二阶段
22:39:52.107 c.TestHandle [pool-1-thread-1] - 第三阶段
22:39:52.107 c.TestHandle [pool-1-thread-1] - 结果:6

测试异常情况:

@Slf4j(topic = "c.TestHandle")
public class TestHandle {
    public static void main(String[] args) {
        ExecutorService pool = Executors.newFixedThreadPool(3);
        CompletableFuture.supplyAsync(() -> {
            log.debug("第一阶段");
            return 1;
        }, pool).handle((v, e) -> {
            int i = 10 / 0;
            log.debug("第二阶段");
            return v + 2;
        }).handle((v, e) -> {
            log.debug("第三阶段");
            if (e != null) {
                log.debug("捕获上一阶段的异常:" + e.getMessage());
            }
            return v + 3;
        }).whenComplete((v, e) -> {
            log.debug("结果:" + v);
        }).exceptionally(e -> {
            log.debug("出现异常:" + e.getMessage());
            return null;
        });

    pool.shutdown();
}

}
22:41:21.347 c.TestHandle [pool-1-thread-1] - 第一阶段
22:41:21.352 c.TestHandle [pool-1-thread-1] - 第三阶段
22:41:21.352 c.TestHandle [pool-1-thread-1] - 捕获上一阶段的异常:java.lang.ArithmeticException: / by zero
22:41:21.353 c.TestHandle [pool-1-thread-1] - 结果:null
22:41:21.353 c.TestHandle [pool-1-thread-1] - 出现异常:java.lang.NullPointerException

注意: 出现的空指针异常(v为null,出现在 return v + 2; 和 return v + 3;两处地方)

thenApply 与 thenCompose 对比

  • thenApply 和 thenCompose 都是将当前阶段的结果传递给下一个阶段,并返回一个新的 CompletionStage
  • 类似于stream中的map和flatMap
public <U> CompletionStage<U>   thenApply(Function<? super T, ? extends U> fn);

public <U> CompletionStage<U> thenCompose(Function<? super T, ? extends CompletionStage<U>> fn);
这两个方法的返回值都是CompletionStage,只是传入函数fn不同
对于thenApply,fn函数是对一个已完成的CompletionStage的返回值进行计算、操作
对于thenCompose,fn函数是对新创建的CompletionStage进行计算、操作

测试:

public static void main(String[] args) {
        CompletableFuture<String> thenApply = CompletableFuture.supplyAsync(() -> 1)
                .thenApply(v -> "将Integer类型值:" + v + ",转换成字符串");CompletableFuture<String> thenCompose = CompletableFuture.supplyAsync(() -> 1).thenCompose(v -> CompletableFuture.supplyAsync(() -> "新创建一个CompletableFuture将Integer类型值:" + v + ",转换成字符串"));System.out.println("thenApply:" +thenApply.join());System.out.println("thenCompose:" + thenCompose.join());
}
thenApply:将Integer类型值:1,转换成字符串
thenCompose:新创建一个CompletableFuture将Integer类型值:1,转换成字符串

whenComplete

  • 返回一个与此阶段具有相同结果或异常的新 CompletionStage,它在此阶段完成时执行给定的操作。
  • 当这个阶段完成时,使用这个阶段的结果(如果没有,则为 null)和异常(如果没有,则为 null)作为参数传递到下一个阶段。
  • 当动作返回时,返回阶段完成。如果提供的操作本身遇到异常,则返回的阶段异常完成并出现此异常,除非此阶段也异常完成。

whenComplete 源码:

public CompletionStage<T> whenComplete(BiConsumer<? super T, ? super Throwable> action);

测试:

  • 不传入线程池,使用默认线程池 —— ForkJoinPool.commonPool()
@Slf4j(topic = "c.TestWhenComplete")
public class TestWhenComplete {
    public static void main(String[] args) throws InterruptedException {
        CompletableFuture.supplyAsync(() -> {
            // 随机数
            int i = ThreadLocalRandom.current().nextInt(10);
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.debug("1s后返回结果:{}", i);
            return i;
        }).whenComplete((v, e) -> {
            if (e == null) {
                log.debug("没有异常,获取结果:{}", v);
            }
        }).exceptionally(e -> {
            log.debug("出现异常:{}", e.getMessage());
            return null;
        });

    log.debug("主线程去执行其他任务。。。");
    log.debug("主线程执行完成");
}

}
18:18:51.912 c.TestWhenComplete [main] - 主线程去执行其他任务。。。
18:18:51.916 c.TestWhenComplete [main] - 主线程执行完成

Process finished with exit code 0
由结果可以看出,主线程停止后,子线程也随之停止
由于ForkJoinPool.commonPool()类似守护线程,还没执行完就随主线程一起关闭了

解决方法:

  1. 让主线程执行完后,停留一段时间
  2. 传入自定义线程池
  • 让主线程执行完后,停留一段时间
@Slf4j(topic = "c.TestWhenComplete")
public class TestWhenComplete {
 public static void main(String[] args) throws InterruptedException {
     CompletableFuture.supplyAsync(() -> {
         // 随机数
         int i = ThreadLocalRandom.current().nextInt(10);
         try {
             TimeUnit.SECONDS.sleep(1);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
         log.debug("1s后返回结果:{}", i);
         return i;
     }).whenComplete((v, e) -> {
         if (e == null) {
             log.debug("没有异常,获取结果:{}", v);
         }
     }).exceptionally(e -> {
         log.debug("出现异常:{}", e.getMessage());
         return null;
     });


 log.debug("主线程去执行其他任务。。。");
 log.debug("主线程执行完成");
 // 默认的线程池 —— ForkJoinPool.commonPool()类似守护线程,主线程关闭后,也会随之关闭
 TimeUnit.SECONDS.sleep(2);
 log.debug("主线程阻塞2s");


 }
}
18:23:24.503 c.TestWhenComplete [main] - 主线程去执行其他任务。。。
18:23:24.506 c.TestWhenComplete [main] - 主线程执行完成
18:23:25.509 c.TestWhenComplete [ForkJoinPool.commonPool-worker-9] - 1s后返回结果:8
18:23:25.511 c.TestWhenComplete [ForkJoinPool.commonPool-worker-9] - 没有异常,获取结果:8
18:23:26.519 c.TestWhenComplete [main] - 主线程阻塞2s

Process finished with exit code 0
  • 传入自定义线程池
@Slf4j(topic = "c.TestWhenComplete")
public class TestWhenComplete {
    public static void main(String[] args) throws InterruptedException {

        ExecutorService pool = null;
        try {
            pool = Executors.newFixedThreadPool(3);
            CompletableFuture.supplyAsync(() -> {
                // 随机数
                int i = ThreadLocalRandom.current().nextInt(10);
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("1s后返回结果:{}", i);
                return i;
            // 传入自定义线程池
            }, pool).whenComplete((v, e) -> {
                if (e == null) {
                    log.debug("没有异常,获取结果:{}", v);
                }
            }).exceptionally(e -> {
                log.debug("出现异常:{}", e.getMessage());
                return null;
            });
    
            log.debug("主线程去执行其他任务。。。");
            log.debug("主线程执行完成");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            pool.shutdown();
        }
    }

}
18:37:39.968 c.TestWhenComplete [main] - 主线程去执行其他任务。。。
18:37:39.970 c.TestWhenComplete [main] - 主线程执行完成
18:37:40.973 c.TestWhenComplete [pool-1-thread-1] - 1s后返回结果:1
18:37:40.975 c.TestWhenComplete [pool-1-thread-1] - 没有异常,获取结果:1
  • 模拟异常
@Slf4j(topic = "c.TestWhenComplete")
public class TestWhenComplete {
    public static void main(String[] args) throws InterruptedException {

        ExecutorService pool = null;
        try {
            pool = Executors.newFixedThreadPool(3);
            CompletableFuture.supplyAsync(() -> {
                // 随机数
                int i = ThreadLocalRandom.current().nextInt(10);
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("1s后返回结果:{}", i);
                if (i > 2) {
                    // 模拟异常
                    i = 10/0;
                }
                return i;
            // 传入自定义线程池
            }, pool).whenComplete((v, e) -> {
                 if (e == null) {
                    log.debug("没有异常,获取结果:{}", v);
                } else {
                    log.debug("捕获上一阶段异常:{}", e.getMessage());
                }
            }).exceptionally(e -> {
                log.debug("出现异常:{}", e.getMessage());
                return null;
            });
    
            log.debug("主线程去执行其他任务。。。");
            log.debug("主线程执行完成");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            pool.shutdown();
        }
    
    }

}
00:04:32.849 c.TestWhenComplete [main] - 主线程去执行其他任务。。。
00:04:32.851 c.TestWhenComplete [main] - 主线程执行完成
00:04:33.849 c.TestWhenComplete [pool-1-thread-1] - 1s后返回结果:4
00:04:33.854 c.TestWhenComplete [pool-1-thread-1] - 捕获上一阶段异常:java.lang.ArithmeticException: / by zero
00:04:33.854 c.TestWhenComplete [pool-1-thread-1] - 出现异常:java.lang.ArithmeticException: / by zero

exceptionally、whenComplete、handle 对比

public CompletionStage<T> exceptionally(Function<Throwable, ? extends T> fn);
public CompletionStage<T> whenComplete(BiConsumer<? super T, ? super Throwable> action);
public <U> CompletionStage<U> handle(BiFunction<? super T, Throwable, ? extends U> fn);
  • exceptionally 处理上一个阶段的异常,并且可以再返回一个值
  • handle 可以获取上一个阶段正常或异常的结果,并且有返回值,让下一个阶段继续执行
  • whenComplete 可以获取上一个阶段正常或异常的结果,没有返回值

(4) 对计算结果进行消费类型的方法

thenAccept

  • 接收任务的处理结果,并消费处理,无返回结果
  • 返回一个新的 CompletionStage,当此阶段正常完成时,将使用此阶段的结果作为提供的操作的参数来执行该新阶段
public CompletionStage<Void> thenAccept(Consumer<? super T> action);

测试:

@Slf4j(topic = "c.TestThenAccept")
public class TestThenAccept {
    public static void main(String[] args) {
        CompletableFuture<Void> thenAccept = CompletableFuture.supplyAsync(() -> {
            log.debug("第一阶段");
            return 1;
        }).thenApply(v -> {
            log.debug("第二阶段");
            return v + 2;
        }).thenApply(v -> {
            log.debug("第三阶段");
            return v + 3;
        }).thenAccept(v -> {
            log.debug("结果:" + v);
        });
        log.debug("返回结果:{}", thenAccept.join());
    }

}
22:52:09.389 c.TestThenAccept [ForkJoinPool.commonPool-worker-1] - 第一阶段
22:52:09.394 c.TestThenAccept [ForkJoinPool.commonPool-worker-1] - 第二阶段
22:52:09.394 c.TestThenAccept [ForkJoinPool.commonPool-worker-1] - 第三阶段
22:52:09.394 c.TestThenAccept [ForkJoinPool.commonPool-worker-1] - 结果:6
22:52:09.394 c.TestThenAccept [main] - 返回结果:null

thenRun

  • 如果任务A执行完后,再执行B时,B不需要A的结果
  • 返回一个新的 CompletionStage,当此阶段正常完成时,执行给定的操作
public CompletionStage<Void> thenRun(Runnable action);

thenRun、thenAccept、thenApply 对比

  • thenRun:如果任务A执行完后,再执行B时,B不需要A的结果
  • thenAccept:如果任务A执行完后,再执行B时,B需要A的结果,并且无返回值
  • thenApply:如果任务A执行完后,再执行B时,B需要A的结果,并且有返回值

测试:

@Slf4j(topic = "c.TestThenRun_ThenAccept_ThenApply")
public class TestThenRun_ThenAccept_ThenApply {

public static void main(String[] args) {
    CompletableFuture<Void> completableFuture1 = CompletableFuture.supplyAsync(() -> {
        return 1;
    }).thenRun(() -> {
        log.debug("做其他任务");
    });
    log.debug("thenRun的结果:" + completableFuture1.join());

    log.debug("===================");

    CompletableFuture<Void> completableFuture2 = CompletableFuture.supplyAsync(() -> {
        return 1;
    }).thenAccept((v) -> {
        log.debug("获取上一阶段的值:" + v);
    });
    log.debug("thenAccept的结果:" + completableFuture2.join());

    log.debug("===================");

    CompletableFuture<Integer> completableFuture3 = CompletableFuture.supplyAsync(() -> {
        return 1;
    }).thenApply((v) -> {
        log.debug("获取上一阶段的值:" + v);
        return v + 2;
    });
   log.debug("thenApply的结果:" + completableFuture3.join());
}

}
22:58:08.055 c.TestThenRun_ThenAccept_ThenApply [main] - 做其他任务
22:58:08.059 c.TestThenRun_ThenAccept_ThenApply [main] - thenRun的结果:null
22:58:08.059 c.TestThenRun_ThenAccept_ThenApply [main] - ===================
22:58:08.060 c.TestThenRun_ThenAccept_ThenApply [main] - 获取上一阶段的值:1
22:58:08.061 c.TestThenRun_ThenAccept_ThenApply [main] - thenAccept的结果:null
22:58:08.061 c.TestThenRun_ThenAccept_ThenApply [main] - ===================
22:58:08.062 c.TestThenRun_ThenAccept_ThenApply [main] - 获取上一阶段的值:1
22:58:08.062 c.TestThenRun_ThenAccept_ThenApply [main] - thenApply的结果:3

注意:

  • 由于各阶段任务做得很快,这时可能就会出现直接让main线程把任务做了,而没有开启新线程
  • 如果在各阶段加一些休眠,可能就会开启新的线程做任务

给各阶段加休眠:

@Slf4j(topic = "c.TestThenRun_ThenAccept_ThenApply")
public class TestThenRun_ThenAccept_ThenApply {

public static void main(String[] args) {
    CompletableFuture<Void> completableFuture1 = CompletableFuture.supplyAsync(() -> {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return 1;
    }).thenRun(() -> {
        log.debug("做其他任务");
    });
    log.debug("thenRun的结果:" + completableFuture1.join());

    log.debug("===================");

    CompletableFuture<Void> completableFuture2 = CompletableFuture.supplyAsync(() -> {
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return 1;
    }).thenAccept((v) -> {
        log.debug("获取上一阶段的值:" + v);
    });
    log.debug("thenAccept的结果:" + completableFuture2.join());

    log.debug("===================");

    CompletableFuture<Integer> completableFuture3 = CompletableFuture.supplyAsync(() -> {
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return 1;
    }).thenApply((v) -> {
        log.debug("获取上一阶段的值:" + v);
        return v + 2;
    });
    log.debug("thenApply的结果:" + completableFuture3.join());
}

}
23:03:51.729 c.TestThenRun_ThenAccept_ThenApply [ForkJoinPool.commonPool-worker-1] - 做其他任务
23:03:51.732 c.TestThenRun_ThenAccept_ThenApply [main] - thenRun的结果:null
23:03:51.732 c.TestThenRun_ThenAccept_ThenApply [main] - ===================
23:03:53.734 c.TestThenRun_ThenAccept_ThenApply [ForkJoinPool.commonPool-worker-1] - 获取上一阶段的值:1
23:03:53.734 c.TestThenRun_ThenAccept_ThenApply [main] - thenAccept的结果:null
23:03:53.734 c.TestThenRun_ThenAccept_ThenApply [main] - ===================
23:03:56.735 c.TestThenRun_ThenAccept_ThenApply [ForkJoinPool.commonPool-worker-1] - 获取上一阶段的值:1
23:03:56.735 c.TestThenRun_ThenAccept_ThenApply [main] - thenApply的结果:3

thenRun 与 thenRunAsync 对比

  • thenRun 使用的线程池会和上一阶段的线程池相同,并且线程相同(虽然是2个阶段,但对于CompletableFuture来说,是一个任务,只需开启一个线程工作)
  • thenRunAsync 使用的线程池是默认的线程池 ForkJoinPool.commonPool(),并且CompletableFuture会把2个阶段的任务当做2个独立的任务(注意:这2个阶段的任务可能由一个线程完成,也可能由2个线程完成,取决于当时的cpu执行情况,所以在CompletableFuture 的很多场景中,可能会出现一个线程做多个阶段的任务)

thenAccept 与 thenAcceptAsync、thenApply 与 thenApplyAsync 同理

thenRunAsync 源码:

// CPU核数是否大于1
private static final boolean useCommonPool =(ForkJoinPool.getCommonPoolParallelism() > 1);
// CPU核数大于1,则使用默认线程池 ForkJoinPool.commonPool()
private static final Executor asyncPool = useCommonPool ?
        ForkJoinPool.commonPool() : new ThreadPerTaskExecutor();
        
public CompletableFuture<Void> thenRunAsync(Runnable action) {
		// CPU核数大于1, asyncPool 使用默认线程池 ForkJoinPool.commonPool()
        return uniRunStage(asyncPool, action);
}

测试 thenRun:

@Slf4j(topic = "c.TestThenRun")
public class TestThenRun {
    public static void main(String[] args) {
        ExecutorService pool = Executors.newFixedThreadPool(2);
        CompletableFuture<Void> completableFuture1 = CompletableFuture.supplyAsync(() -> {
            log.debug("不传入自定义线程池时,supplyAsync 的线程池:" + Thread.currentThread().getName());
            return 1;
        }).thenRun(() -> {
            log.debug("thenRun 和上一阶段使用相同的线程池:" + Thread.currentThread().getName());
        });

        log.debug("结果:" + completableFuture1.join());
    
        log.debug("======================");
    
        CompletableFuture<Void> completableFuture2 = CompletableFuture.supplyAsync(() -> {
            log.debug("传入自定义线程池时,supplyAsync 的线程池:" + Thread.currentThread().getName());
            return 1;
            // 传入自定义线程池
        }, pool).thenRun(() -> {
            log.debug("thenRun 和上一阶段使用相同的线程池:" + Thread.currentThread().getName());
        });
    
        log.debug("结果:" + completableFuture2.join());
        log.debug("======================");
        // 关闭线程池
        pool.shutdown();
    }

}
23:08:39.413 c.TestThenRun [ForkJoinPool.commonPool-worker-1] - 不传入自定义线程池时,supplyAsync 的线程池:ForkJoinPool.commonPool-worker-1
23:08:39.424 c.TestThenRun [ForkJoinPool.commonPool-worker-1] - thenRun 和上一阶段使用相同的线程池:ForkJoinPool.commonPool-worker-1
23:08:39.425 c.TestThenRun [main] - 结果:null
23:08:39.425 c.TestThenRun [main] - ======================
23:08:39.426 c.TestThenRun [pool-1-thread-1] - 传入自定义线程池时,supplyAsync 的线程池:pool-1-thread-1
23:08:39.426 c.TestThenRun [pool-1-thread-1] - thenRun 和上一阶段使用相同的线程池:pool-1-thread-1
23:08:39.426 c.TestThenRun [main] - 结果:null
23:08:39.426 c.TestThenRun [main] - ======================

测试 thenRunAsync:

@Slf4j(topic = "c.TestThenRunAsync")
public class TestThenRunAsync {
    public static void main(String[] args) {
        ExecutorService pool = Executors.newFixedThreadPool(2);
        CompletableFuture<Void> completableFuture1 = CompletableFuture.supplyAsync(() -> {
            log.debug("不传入自定义线程池时,supplyAsync 的线程池:" + Thread.currentThread().getName());
            return 1;
        }).thenRunAsync(() -> {
            log.debug("thenRunAsync 使用默认的线程池:" + Thread.currentThread().getName());
        });

        log.debug("结果:" + completableFuture1.join());
    
        log.debug("======================");
    
        CompletableFuture<Void> completableFuture2 = CompletableFuture.supplyAsync(() -> {
            log.debug("传入自定义线程池时,supplyAsync 的线程池:" + Thread.currentThread().getName());
            return 1;
            // 传入自定义线程池
        }, pool).thenRunAsync(() -> {
            log.debug("thenRunAsync 使用默认的线程池:" + Thread.currentThread().getName());
        });
    
        log.debug("结果:" + completableFuture2.join());
        log.debug("======================");
        // 关闭线程池
        pool.shutdown();
    }

}
23:10:21.374 c.TestThenRunAsync [ForkJoinPool.commonPool-worker-1] - 不传入自定义线程池时,supplyAsync 的线程池:ForkJoinPool.commonPool-worker-1
23:10:21.377 c.TestThenRunAsync [ForkJoinPool.commonPool-worker-1] - thenRunAsync 使用默认的线程池:ForkJoinPool.commonPool-worker-1
23:10:21.377 c.TestThenRunAsync [main] - 结果:null
23:10:21.377 c.TestThenRunAsync [main] - ======================
23:10:21.378 c.TestThenRunAsync [pool-1-thread-1] - 传入自定义线程池时,supplyAsync 的线程池:pool-1-thread-1
23:10:21.378 c.TestThenRunAsync [ForkJoinPool.commonPool-worker-1] - thenRunAsync 使用默认的线程池:ForkJoinPool.commonPool-worker-1
23:10:21.378 c.TestThenRunAsync [main] - 结果:null
23:10:21.379 c.TestThenRunAsync [main] - ======================

(5) 对计算速度进行选用类型的方法

applyToEither

  • 返回最先完成任务的结果

applyToEither、acceptEither、runAfterEither 对比

  • applyToEither 得到最先完成任务的结果,有返回值
  • acceptEither 得到最先完成任务的结果,没有返回值
  • runAfterEither 不关心最先完成任务的结果,没有返回值
public <U> CompletionStage<U> applyToEither(CompletionStage<? extends T> other, Function<? super T, U> fn);
public CompletionStage<Void> acceptEither(CompletionStage<? extends T> other, Consumer<? super T> action);
public CompletionStage<Void> runAfterEither(CompletionStage<?> other, Runnable action);

测试 applyToEither:

@Slf4j(topic = "c.TestApplyToEither")
public class TestApplyToEither {
    public static void main(String[] args) {
        CompletableFuture<String> A = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.debug("A 1s 完成任务");
            return "A";
        });

        CompletableFuture<String> B = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.debug("B 2s 完成任务");
            return "B";
        });
    
        CompletableFuture<String> result = A.applyToEither(B, v -> v + " 先完成");
        log.debug(result.join());
    }

}
23:15:50.711 c.TestApplyToEither [ForkJoinPool.commonPool-worker-1] - A 1s 完成任务
23:15:50.714 c.TestApplyToEither [main] - A 先完成

(6) 对计算结果进行合并类型的方法

thenCombine

  • 合并2个阶段正常完成的结果
  • 先完成的先等待

thenCombine、thenAcceptBoth、runAfterBoth 对比

  • thenCombine 合并前2个阶段正常完成的结果,有返回值
  • thenAcceptBoth 合并前2个阶段正常完成的结果,没有返回值
  • runAfterBoth 不关心前2个阶段正常完成的结果,没有返回值
public <U,V> CompletionStage<V> thenCombine(CompletionStage<? extends U> other,  BiFunction<? super T,? super U,? extends V> fn);
public <U> CompletionStage<Void> thenAcceptBoth(CompletionStage<? extends U> other, BiConsumer<? super T, ? super U> action);
public CompletionStage<Void> runAfterBoth(CompletionStage<?> other, Runnable action);

测试 thenCombine:

  • 第一种写法
@Slf4j(topic = "c.TestThenCombine")
public class TestThenCombine {
    public static void main(String[] args) {
        log.debug("开始");
        CompletableFuture<Integer> A = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.debug("A 完成");
            return 10;
        });

        CompletableFuture<Integer> B = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.debug("B 完成");
            return 20;
        });
    
        CompletableFuture<Integer> result = A.thenCombine(B, (x, y) -> x + y);
        log.debug("结果:" + result.join());
    }

}
16:47:50.931 c.TestThenCombine [main] - 开始
16:47:51.992 c.TestThenCombine [ForkJoinPool.commonPool-worker-9] - A 完成
16:47:53.007 c.TestThenCombine [ForkJoinPool.commonPool-worker-2] - B 完成
16:47:53.007 c.TestThenCombine [main] - 结果:30
  • 第二种写法
@Slf4j(topic = "c.TestThenCombine")
public class TestThenCombine {
    public static void main(String[] args) {
        log.debug("开始");
        CompletableFuture<Integer> result = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.debug("A 完成");
            return 10;
        }).thenCombine(CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.debug("B 完成");
            return 20;
        }), (x, y) -> x + y);

        log.debug("结果:" + result.join());
    }

}
16:53:50.817 c.TestThenCombine [main] - 开始
16:53:51.894 c.TestThenCombine [ForkJoinPool.commonPool-worker-9] - A 完成
16:53:52.886 c.TestThenCombine [ForkJoinPool.commonPool-worker-2] - B 完成
16:53:52.886 c.TestThenCombine [main] - 结果:30

6.性能分析

分析 CompletableFuture 完成异步任务

  • 当 任务数 等于 CompletableFuture 线程池的最大线程数(CPU核心数 - 1) 时,CompletableFuture 性能最佳

    例如:有4个核心,则 CompletableFuture 线程池的最大线程数是3,此时任务数为3(假设每个任务耗时1s),CompletableFuture 性能最 佳(CompletableFuture 做完3个任务耗时也就1s左右);

    如果任务数为4-6,则 CompletableFuture 做完4-6个任务耗时就会是2s左右;

    如果任务数为7-9,则 CompletableFuture 做完7-9个任务耗时就会是3s左右;

    以此类推

  • 所以当已知任务数量时,可以创建自定义线程池,手动设置线程池最大线程数

  • 如果需要根据任务数量动态设置线程池最大线程数时,每执行完一批任务后,需要关闭线程池,等下一次新任务来时再根据任务数重新创建线程池

    例如:第一次来了100个任务,则创建10个线程的线程池,执行完100个任务后关闭线程池;第二次来了200个任务时,再重新创建有20个线程的线程池;

测试任务数等于 CompletableFuture 线程池的最大线程数:

创建任务类:

@Slf4j(topic = "c.Task")
class Task {
    // 任务名
    private String name;
    // 做该任务耗时 (秒)
    private Integer productionTime;

    public Task(String name, Integer productionTime) {
        this.name = name;
        this.productionTime = productionTime;
    }
    
    // 做任务
    public void doTask() {
        try {
            TimeUnit.SECONDS.sleep(this.productionTime);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.debug(this.name + " 已做完");
    }

}

第一种写法:

@Slf4j(topic = "c.TestCompletableFuture_doTask")
public class TestCompletableFuture_doTask {
    public static void main(String[] args) {
		log.debug("该二手台式机可用处理器数量:{}", Runtime.getRuntime().availableProcessors());
		log.debug("当前线程池中的最大线程数:{}", ForkJoinPool.getCommonPoolParallelism());
        log.debug("开始");
        long startTime = System.currentTimeMillis();
        // 创建任务
        List<Task> tasks = new ArrayList<>();
        for (int i = 1; i <= 3; i++) {
            Task task = new Task("任务" + i, 1);
            tasks.add(task);
        }
        // 做任务
        List<CompletableFuture> completableFutureList = new ArrayList<>();
        for (Task task : tasks) {
            CompletableFuture<Void> cf = CompletableFuture.runAsync(() -> task.doTask());
            // 将所有任务加到 CompletableFuture 集合中
            completableFutureList.add(cf);
        }
        // 等待 CompletableFuture 集合中的所有任务执行完毕
        CompletableFuture.allOf(completableFutureList.toArray(new CompletableFuture[completableFutureList.size()])).join();

        log.debug("做完所有任务的耗时:{}", (System.currentTimeMillis() - startTime));
    
    }

}
00:50:41.392 c.TestCompletableFuture_doTask [main] - 该二手台式机可用处理器数量:4
00:50:41.392 c.TestCompletableFuture_doTask [main] - 当前线程池中的最大线程数:3
00:50:41.397 c.TestCompletableFuture_doTask [main] - 开始
00:50:42.467 c.Task [ForkJoinPool.commonPool-worker-3] - 任务3 已做完
00:50:42.467 c.Task [ForkJoinPool.commonPool-worker-2] - 任务2 已做完
00:50:42.467 c.Task [ForkJoinPool.commonPool-worker-1] - 任务1 已做完
00:50:42.467 c.TestCompletableFuture_doTask [main] - 做完所有任务的耗时:1069

第二种写法(用Stream简化代码,结果还是一样,主要是简化代码):

@Slf4j(topic = "c.TestCompletableFuture_doTask")
public class TestCompletableFuture_doTask {
    public static void main(String[] args) {
		log.debug("该二手台式机可用处理器数量:{}", Runtime.getRuntime().availableProcessors());
        log.debug("当前线程池中的最大线程数:{}", ForkJoinPool.getCommonPoolParallelism());
        log.debug("开始");
        long startTime = System.currentTimeMillis();
        // 将创建的任务加到 CompletableFuture 数组中 并执行
        CompletableFuture[] tasks= IntStream.rangeClosed(1, 3)
                .mapToObj(i -> new Task("任务" + i, 1))
                .map(task -> CompletableFuture.runAsync(task::doTask))
                .toArray(CompletableFuture[]::new);
		// 等待 CompletableFuture 数组中的所有任务执行完
        CompletableFuture.allOf(tasks).join();

        log.debug("做完所有任务的耗时:{}", (System.currentTimeMillis() - startTime));
    }

}
00:51:13.655 c.TestCompletableFuture_doTask [main] - 该二手台式机可用处理器数量:4
00:51:13.655 c.TestCompletableFuture_doTask [main] - 当前线程池中的最大线程数:3
00:51:13.667 c.TestCompletableFuture_doTask [main] - 开始
00:51:14.896 c.Task [ForkJoinPool.commonPool-worker-2] - 任务2 已做完
00:51:14.896 c.Task [ForkJoinPool.commonPool-worker-1] - 任务1 已做完
00:51:14.896 c.Task [ForkJoinPool.commonPool-worker-3] - 任务3 已做完
00:51:14.896 c.TestCompletableFuture_doTask [main] - 做完所有任务的耗时:1229

测试任务数超过 CompletableFuture线程池的最大线程数:

@Slf4j(topic = "c.TestCompletableFuture_doTask")
public class TestCompletableFuture_doTask {
    public static void main(String[] args) {
		log.debug("该二手台式机可用处理器数量:{}", Runtime.getRuntime().availableProcessors());
        log.debug("当前线程池中的最大线程数:{}", ForkJoinPool.getCommonPoolParallelism());
        log.debug("开始");
        long startTime = System.currentTimeMillis();
        // 将创建的任务加到 CompletableFuture 数组中 并执行
        CompletableFuture[] tasks= IntStream.rangeClosed(1, 3)
                .mapToObj(i -> new Task("任务" + i, 1))
                .map(task -> CompletableFuture.runAsync(task::doTask))
                .toArray(CompletableFuture[]::new);
		// 等待 CompletableFuture 数组中的所有任务执行完
        CompletableFuture.allOf(tasks).join();

        log.debug("做完所有任务的耗时:{}", (System.currentTimeMillis() - startTime));
    }

}
01:06:31.516 c.TestCompletableFuture_doTask [main] - 该二手台式机可用处理器数量:4
01:06:31.516 c.TestCompletableFuture_doTask [main] - 当前线程池中的最大线程数:3
01:06:31.527 c.TestCompletableFuture_doTask [main] - 开始
01:06:32.636 c.Task [ForkJoinPool.commonPool-worker-2] - 任务2 已做完
01:06:32.636 c.Task [ForkJoinPool.commonPool-worker-1] - 任务1 已做完
01:06:32.637 c.Task [ForkJoinPool.commonPool-worker-3] - 任务3 已做完
01:06:33.637 c.Task [ForkJoinPool.commonPool-worker-2] - 任务4 已做完
01:06:33.637 c.TestCompletableFuture_doTask [main] - 做完所有任务的耗时:2110

CompletableFuture 配合自定义线程池使用

@Slf4j(topic = "c.TestCompletableFuture_doTask")
public class TestCompletableFuture_doTask {
    public static void main(String[] args) {
		ExecutorService pool = Executors.newCachedThreadPool();
        log.debug("开始");
        long startTime = System.currentTimeMillis();
        // 将创建的任务加到 CompletableFuture 数组中 并执行
        CompletableFuture[] tasks= IntStream.rangeClosed(1, 3)
                .mapToObj(i -> new Task("任务" + i, 1))
                .map(task -> CompletableFuture.runAsync(task::doTask, pool)) // 传入自定义线程池
                .toArray(CompletableFuture[]::new);
		// 等待 CompletableFuture 数组中的所有任务执行完
        CompletableFuture.allOf(tasks).join();

        log.debug("做完所有任务的耗时:{}", (System.currentTimeMillis() - startTime));
        pool.shutdown();
    }

}
01:32:25.794 c.TestCompletableFuture_doTask [main] - 开始
01:32:26.871 c.Task [pool-1-thread-10] - 任务10 已做完
01:32:26.872 c.Task [pool-1-thread-9] - 任务9 已做完
01:32:26.872 c.Task [pool-1-thread-8] - 任务8 已做完
01:32:26.872 c.Task [pool-1-thread-7] - 任务7 已做完
01:32:26.872 c.Task [pool-1-thread-6] - 任务6 已做完
01:32:26.873 c.Task [pool-1-thread-5] - 任务5 已做完
01:32:26.873 c.Task [pool-1-thread-4] - 任务4 已做完
01:32:26.873 c.Task [pool-1-thread-3] - 任务3 已做完
01:32:26.874 c.Task [pool-1-thread-2] - 任务2 已做完
01:32:26.874 c.Task [pool-1-thread-1] - 任务1 已做完
01:32:26.874 c.TestCompletableFuture_doTask [main] - 做完所有任务的耗时:1080
可以看到就算做10个任务,耗时也是1s左右,但不是任何数量的任务都只耗时1s
一台计算机,一瞬间处理的最大任务数量跟CPU的处理能力有关,也跟内存、硬盘、网卡等设备有关
具体跟什么有关,取决于任务本身所需要的资源
由于测试案例中的任务使用sleep模拟耗时的,但是sleep这个操作不太占CPU资源,
所以当传入自定义线程池后,任务数超过了CPU核心数后,耗时也不多。

01:06:33.637 c.TestCompletableFuture_doTask [main] - 做完所有任务的耗时:2110




## CompletableFuture 配合自定义线程池使用

```java
@Slf4j(topic = "c.TestCompletableFuture_doTask")
public class TestCompletableFuture_doTask {
    public static void main(String[] args) {
		ExecutorService pool = Executors.newCachedThreadPool();
        log.debug("开始");
        long startTime = System.currentTimeMillis();
        // 将创建的任务加到 CompletableFuture 数组中 并执行
        CompletableFuture[] tasks= IntStream.rangeClosed(1, 3)
                .mapToObj(i -> new Task("任务" + i, 1))
                .map(task -> CompletableFuture.runAsync(task::doTask, pool)) // 传入自定义线程池
                .toArray(CompletableFuture[]::new);
		// 等待 CompletableFuture 数组中的所有任务执行完
        CompletableFuture.allOf(tasks).join();

        log.debug("做完所有任务的耗时:{}", (System.currentTimeMillis() - startTime));
        pool.shutdown();
    }

}
01:32:25.794 c.TestCompletableFuture_doTask [main] - 开始
01:32:26.871 c.Task [pool-1-thread-10] - 任务10 已做完
01:32:26.872 c.Task [pool-1-thread-9] - 任务9 已做完
01:32:26.872 c.Task [pool-1-thread-8] - 任务8 已做完
01:32:26.872 c.Task [pool-1-thread-7] - 任务7 已做完
01:32:26.872 c.Task [pool-1-thread-6] - 任务6 已做完
01:32:26.873 c.Task [pool-1-thread-5] - 任务5 已做完
01:32:26.873 c.Task [pool-1-thread-4] - 任务4 已做完
01:32:26.873 c.Task [pool-1-thread-3] - 任务3 已做完
01:32:26.874 c.Task [pool-1-thread-2] - 任务2 已做完
01:32:26.874 c.Task [pool-1-thread-1] - 任务1 已做完
01:32:26.874 c.TestCompletableFuture_doTask [main] - 做完所有任务的耗时:1080
可以看到就算做10个任务,耗时也是1s左右,但不是任何数量的任务都只耗时1s
一台计算机,一瞬间处理的最大任务数量跟CPU的处理能力有关,也跟内存、硬盘、网卡等设备有关
具体跟什么有关,取决于任务本身所需要的资源
由于测试案例中的任务使用sleep模拟耗时的,但是sleep这个操作不太占CPU资源,
所以当传入自定义线程池后,任务数超过了CPU核心数后,耗时也不多。

你可能感兴趣的:(JUC,java,jvm,nio,java-ee,junit)