CompletableFuture

前置知识

JDK 1.5 新增了 Future 接口,用于描述异步计算的接口。其主功能如下所示:

// 尝试停止当前任务的执行,如果任务已完成、已被取消或由于某些其他原因无法取消,则此尝试将失败。
boolean cancel(boolean mayInterruptIfRunning);
// 如果此任务在正常完成之前被取消,则返回 ture。
boolean isCancelled();
// 如果此任务完成,则返回 ture
boolean isDone();
// 等待计算完成,然后检索其结果。
V get() throws InterruptedException, ExecutionException;
// 最多等待给定时间以完成计算,然后检索其结果
V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;

通过查看源码可以得出 Future 用于获取异步结果的方法较少:

  1. 通过调用 get 方法去获取异步结果,但是这样会造成阻塞。还有就是使用设置超时时间的 get 方法去获取异步结果。
  2. 使用轮询调用 isDone 方法,确认线程是否已经执行完成然后再去获取异步结果。

以上两种方式都有其缺点,一种是会造成阻塞,而另一种会耗费无谓的CPU资源,而且也不见得能及时地得到计算结果。这两种方式有违异步编程的初衷。

在 JDK 1.8 的时候新出了 CompletableFuture 接口,其作为 Future 的扩展。提供了函数式编程能力,简化了异步编程的复杂性,通过回调的方式处理计算结果,并提供了完善的异常处理手段。默认依靠fork/join框架启动新的线程实现异步与并发。

类架构说明:

image

CompletableFuture 即实现了 Future 接口又实现了 CompletionStage 接口,而其 CompletionStage 代表异步计算过程中的某一个阶段,一个阶段完成以后可能会触发另外一个阶段,有些类似Linux系统的管道分隔符传参数。

案例分析

before

普通的写法

没有使用 CompletableFuture 和 future,一步步的去处理。

public class CompletableFutureMallDemo {

    static List <NetMall> list = Arrays.asList(
            new NetMall("jd"),
            new NetMall("dangdang"),
            new NetMall("taobao"),
            new NetMall("pdd"),
            new NetMall("xianyu")
    );

    /**
     * step by step 一家家搜查
     * */
    public static List <String> getPrice(List <NetMall> list, String productName) {
        return list.stream().map(
                item -> String.format(
                        productName + " in %s price is %.2f", item.getNetMallName(),
                        item.calcPrice(productName))
        ).collect(Collectors.toList());
    }

    public static void main(String[] args) {
        long startTime = System.currentTimeMillis();
        List <String> stringList = getPrice(list, "mysql");
        stringList.forEach(System.out::println);
        long endTime = System.currentTimeMillis();
        System.out.println("---- costTime:" + (endTime - startTime) + "毫秒");
    }

}


class NetMall {

    private String netMallName;

    public String getNetMallName() {
        return netMallName;
    }

    public NetMall(String netMallName) {
        this.netMallName = netMallName;
    }

    public double calcPrice(String productName) {
        try {
            // 暂停线程 1 秒
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 使用 ThreadLocalRandom 生成随机数
        return ThreadLocalRandom.current().nextDouble() * 2 + productName.charAt(0);
    }
}

结果输出

mysql in jd price is 109.67
mysql in dangdang price is 109.60
mysql in taobao price is 110.30
mysql in pdd price is 110.08
mysql in xianyu price is 109.46
---- costTime:5086毫秒

使用 future

public class CompletableFutureMallDemo {

    static List <NetMall> list = Arrays.asList(
            new NetMall("jd"),
            new NetMall("dangdang"),
            new NetMall("taobao"),
            new NetMall("pdd"),
            new NetMall("xianyu")
    );

    public static List <String> getPriceFuture(List <NetMall> list, String productName) {
        List<FutureTask<String>> futureTasks = new ArrayList <FutureTask<String>>();
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 10, 3,
                TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(3),
                new ThreadPoolExecutor.DiscardOldestPolicy());
        threadPool.prestartAllCoreThreads(); // 预启动所有核心线程
        list.forEach(item -> {
            threadTask threadTask = new threadTask(item, productName);
            FutureTask <String> futureTask = new FutureTask <String>(threadTask);
            futureTasks.add(futureTask);
            threadPool.submit(futureTask);
        });
        List <String> collect = futureTasks.stream().map(item -> {
            try {
                return item.get();
            } catch (InterruptedException | ExecutionException e) {
                throw new RuntimeException(e);
            }
        }).collect(Collectors.toList());
        //清理线程池
        threadPool.shutdown();
        return collect;
    }

    public static void main(String[] args) {
        long startTime3 = System.currentTimeMillis();
        List <String> stringList3 = getPriceFuture(list, "mysql");
        stringList3.forEach(System.out::println);
        long endTime3 = System.currentTimeMillis();
        System.out.println("---- costTime:" + (endTime3 - startTime3) + "毫秒");
    }

}

class threadTask implements Callable <String> {

    NetMall netMall;

    String productName;

    public threadTask() {
    }

    public threadTask(NetMall netMall, String productName) {
        this.netMall = netMall;
        this.productName = productName;
    }

    @Override
    public String call() throws Exception {
        return String.format(productName + " in %s price is %.2f", netMall.getNetMallName(), netMall.calcPrice(productName));
    }
}

class NetMall {

    private String netMallName;

    public String getNetMallName() {
        return netMallName;
    }

    public NetMall(String netMallName) {
        this.netMallName = netMallName;
    }

    public double calcPrice(String productName) {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return ThreadLocalRandom.current().nextDouble() * 2 + productName.charAt(0);
    }
}

输出结果

mysql in jd price is 109.15
mysql in dangdang price is 109.39
mysql in taobao price is 109.64
mysql in pdd price is 109.26
mysql in xianyu price is 109.91
---- costTime:1014毫秒

after

使用 CompletableFuture 采用异步多线程去处理。

public class CompletableFutureMallDemo {

    static List <NetMall> list = Arrays.asList(
            new NetMall("jd"),
            new NetMall("dangdang"),
            new NetMall("taobao"),
            new NetMall("pdd"),
            new NetMall("xianyu")
    );

    
    public static List <String> getPriceByCompletableFuture(List <NetMall> list, String productName) {
        return list.stream().map(netMall -> CompletableFuture.supplyAsync(
                () -> String.format(
                        productName + " in %s price is %.2f", netMall.getNetMallName(), netMall.calcPrice(productName)))
        ).collect(Collectors.toList()).stream().map(CompletableFuture::join).collect(Collectors.toList());
    }

    public static void main(String[] args) {
        long startTime2 = System.currentTimeMillis();
        List <String> stringList2 = getPriceByCompletableFuture(list, "mysql");
        stringList2.forEach(System.out::println);
        long endTime2 = System.currentTimeMillis();
        System.out.println("---- costTime:" + (endTime2 - startTime2) + "毫秒");
    }

}


class NetMall {

    private String netMallName;

    public String getNetMallName() {
        return netMallName;
    }

    public NetMall(String netMallName) {
        this.netMallName = netMallName;
    }

    public double calcPrice(String productName) {
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return ThreadLocalRandom.current().nextDouble() * 2 + productName.charAt(0);
    }
}

输出结果

mysql in jd price is 109.71
mysql in dangdang price is 110.25
mysql in taobao price is 110.16
mysql in pdd price is 109.80
mysql in xianyu price is 110.43
---- costTime:1012毫秒

CompletableFuture vs Future vs 普通方式

  1. CompletableFuture 和 Future 相较于普通方式支持异步多线程调用,处理速度更快;
  2. CompletableFuture 提供了函数式编程的能力,完美结合了Java8流的新特性,代码更简练,语义更清晰;
  3. CompletableFuture 支持回调函数,极大丰富了Future的功能;
  4. CompletableFuture 支持合并相互独立的异步计算结果、等待所有异步任务完成或者等待其中一个异步任务完成就返回等多种场景。
  5. CompletableFuture 提供了原生的异常处理API。

CompletionStage 接口

CompletionStage 定义了一组接口用于在一个阶段执行结束之后,要么继续执行下一个阶段,要么对结果进行转换产生新的结果等等,一般来说要执行下一个阶段都需要上一个阶段正常完成,当然也提供了对异常结果的处理接口。

CompletionStage 的接口一般都返回新的 CompletionStage,表示执行完一些逻辑后,生成新的CompletionStage,构成链式的阶段型的操作。目前只有 CompletableFuture 一个实现类。

CompletionStage 中的方法主要用来定义一个行为,行为的入 参也大量的使用了函数式接口的命名方式,可以是 Consumer、Function、Runable 等。相关方法比如有 apply,accept,run 等,这些方法的区别在于它们有些是需要传入参,有些则会产生“结果”。

  • Funtion 接受一个参数,并且又返回值
  • Comsumer 接受一个参数,没有返回值
  • Runable 既不产生结果也不消耗结果
  • Supplier 没有参数,有一个返回值
  • BiConsumer 接受两个参数,没有返回值

简单来说 CompletionStage 确保了 CompletableFuture 能够进行链式调用。

CompletableFuture 详解

创建方法

supplyAsync 方法

执行任务,支持返回值。

//使用默认内置线程池ForkJoinPool.commonPool(),根据supplier构建执行任务
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
//自定义线程,根据supplier构建执行任务
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)

runAsync 方法

执行任务,没有返回值。

//使用默认内置线程池ForkJoinPool.commonPool(),根据runnable构建执行任务
public static CompletableFuture<Void> runAsync(Runnable runnable) 
//自定义线程,根据runnable构建执行任务
public static CompletableFuture<Void> runAsync(Runnable runnable,  Executor executor)

获取结果的方法

get() 和 get(long timeout, TimeUnit unit)

在 Future 中就已经提供了,后者提供超时处理,如果在指定时间内未获取结果将抛出异常。

getNow

立即获取结果不阻塞,结果计算已完成将返回结果或计算过程中的异常,如果未计算完成将返回设定的valueIfAbsent值

join

方法里不会抛出异常,其余的的和 get 方法类似

示例代码

	@Test
    public void testCompletableGet() throws ExecutionException, InterruptedException {
        CompletableFuture <String> cp1 = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "商品A";
        });

        // getNow方法测试
        System.out.println(cp1.getNow("商品B"));

        //join方法测试
        CompletableFuture<Integer> cp2 = CompletableFuture.supplyAsync((() -> 1 / 0));
        System.out.println(cp2.join());
        System.out.println("-----------------------------------------------------");
        //get方法测试
        CompletableFuture<Integer> cp3 = CompletableFuture.supplyAsync((() -> 1 / 0));
        System.out.println(cp3.get());
    }

运行结果

  • 第一个执行结果为 「商品B」,因为要先睡上1秒结果不能立即获取
  • join方法获取结果方法里不会抛异常,但是执行结果会抛异常,抛出的异常为CompletionException
  • get方法获取结果方法里将抛出异常,执行结果抛出的异常为ExecutionException

异步回调方法

带 Async 与不加的区别:

如果你执行第一个任务的时候,传入了一个自定义的线程池:

  • 调用thenRun方法执行第二个任务时,则第二个任务和第一个任务是共用同一个线程池。
  • 调用thenRunAsync执行第二个任务时,则第一个任务使用的是你自己传入的线程池,第二个任务使用的是ForkJoin线程池。

说明:后面介绍的thenAccept 和 thenAcceptAsync,thenApply 和thenApplyAsync 等,它们之间的区别也是这个。

thenRun/thenRunAsync

不依赖上一个任务的返回结果,无传参,无返回值

	@Test
    public void testCompletableFutureThenRun() throws Exception {
        StopWatch stopWatch = new StopWatch("执行 thenRun");
        stopWatch.start();
        CompletableFuture <Void> future1 = CompletableFuture.runAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        CompletableFuture <Void> future2 = future1.thenRun(() -> {
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });
        System.out.println(future2.get());
        TimeUnit.SECONDS.sleep(3);
        stopWatch.split();
        System.out.printf("%s 耗时一共: %s%n", stopWatch.getMessage(), stopWatch.getSplitTime());
    }

	/** 
     * null
     * 执行 thenRun 耗时一共: 6 秒
     * */

thenAccept/thenAcceptAsync

第一个任务执行完成后,执行第二个回调方法任务,会将该任务的执行结果,作为入参,传递到回调方法中,但是回调方法是没有返回值的。

	@Test
    public void testCompletableFutureThenAccept() throws Exception {
        StopWatch stopWatch = new StopWatch("执行 thenRun");
        stopWatch.start();
        CompletableFuture <Integer> future1 = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return 1;
        });
        CompletableFuture <Void> future2 = future1.thenAccept((v) -> {
            try {
                System.out.printf("future1 的返回结果: %s%n", v);
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });
        System.out.println(future2.get());
        TimeUnit.SECONDS.sleep(3);
        stopWatch.split();
        System.out.printf("%s 耗时一共: %s 秒%n", stopWatch.getMessage(), stopWatch.getSplitTime()/1000);
    }
    /** 
     * future1 的返回结果: 1
     * null
     * 执行 thenRun 耗时一共: 6 秒
     * */

thenApply/thenApplyAsync

表示第一个任务执行完成后,执行第二个回调方法任务,会将该任务的执行结果,作为入参,传递到回调方法中,并且回调方法是有返回值的。

    @Test
    public void testCompletableFutureThenApply() throws Exception {
        StopWatch stopWatch = new StopWatch("执行 thenRun");
        stopWatch.start();
        CompletableFuture <Integer> future1 = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return 1;
        });
        CompletableFuture <Integer> future2 = future1.thenApply((v) -> {
            try {
                System.out.printf("future1 的返回结果: %s%n", v);
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            return 10;
        });
        System.out.println(future2.get());
        TimeUnit.SECONDS.sleep(3);
        stopWatch.split();
        System.out.printf("%s 耗时一共: %s 秒%n", stopWatch.getMessage(), stopWatch.getSplitTime()/1000);
    }
    /**
     * future1 的返回结果: 1
     * 10
     * 执行 thenRun 耗时一共: 6 秒
     * */

异常回调

当CompletableFuture的任务不论是正常完成还是出现异常它都会调用 whenComplete 这回调函数。

  • 正常完成:whenComplete 返回结果和上级任务一致,异常为null;
  • 出现异常:whenComplete返回结果为null,异常为上级任务的异常;

即调用get()时,正常完成时就获取到结果,出现异常时就会抛出异常,需要你处理该异常。

whenComplete

	@Test
    public void testCompletableFutureWhenCompleted() throws Exception {
        CompletableFuture <Double> future1 = CompletableFuture.supplyAsync(() -> {
            if (ThreadLocalRandom.current().nextDouble() < 0.5) {
                throw new RuntimeException("出错了!");
            }
            return 0.5;
        }).whenComplete((v, e) -> {
            if (v == null) {
                System.out.println("whenComplete aDouble is null");
            } else {
                System.out.println("whenComplete aDouble is " + v);
            }
            if (e == null) {
                System.out.println("whenComplete throwable is null");
            } else {
                System.out.println("whenComplete throwable is " + e.getMessage());
            }
        });
        System.out.println("最终返回的结果 = " + future1.get());
    }
正常完成,没有异常
whenComplete aDouble is 0.5
whenComplete throwable is null
最终返回的结果 = 0.5
出现异常时:get() 会抛出异常
whenComplete aDouble is null
whenComplete throwable is java.lang.RuntimeException: 出错了!

java.util.concurrent.ExecutionException: java.lang.RuntimeException: 出错了!
at java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:357)
	at java.util.concurrent.CompletableFuture.get(CompletableFuture.java:1908)

whenComplete + exceptionally

	@Test
    public void testCompletableFutureWhenCompletedExceptionally() throws Exception {
        CompletableFuture <Double> future1 = CompletableFuture.supplyAsync(() -> {
            if (ThreadLocalRandom.current().nextDouble() < 0.5) {
                throw new RuntimeException("出错了!");
            }
            return 0.5;
        }).whenComplete((v, e) -> {
            if (v == null) {
                System.out.println("whenComplete aDouble is null");
            } else {
                System.out.println("whenComplete aDouble is " + v);
            }
            if (e == null) {
                System.out.println("whenComplete throwable is null");
            } else {
                System.out.println("whenComplete throwable is " + e.getMessage());
            }
        }).exceptionally(e ->{
            System.out.println("exceptionally中异常:" + e.getMessage());
            return 0.0;
        });
        System.out.println("最终返回的结果 = " + future1.get());
    }

当出现异常时,exceptionally中会捕获该异常,给出默认返回值0.0。

whenComplete aDouble is null
whenComplete throwable is java.lang.RuntimeException: 出错了!
exceptionally中异常:java.lang.RuntimeException: 出错了!
最终返回的结果 = 0.0

多任务组合回调

AND 组合关系

thenCombine / thenAcceptBoth / runAfterBoth 都表示:「当任务一和任务二都完成再执行任务三」

区别在于:

  • runAfterBoth:不会把执行结果当做方法入参,且没有返回值;

  • thenAcceptBoth:会将两个任务的执行结果作为方法入参,传递到指定方法中,且无返回值;

  • thenCombine:会将两个任务的执行结果作为方法入参,传递到指定方法中,且有返回值

实例代码
@Test
    public void testCompletableThenCombine() throws ExecutionException, InterruptedException {
        //创建线程池
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        //开启异步任务1
        CompletableFuture<Integer> task = CompletableFuture.supplyAsync(() -> {
            System.out.println("异步任务1,当前线程是:" + Thread.currentThread().getId());
            int result = 1 + 1;
            System.out.println("异步任务1结束");
            return result;
        }, executorService);

        //开启异步任务2
        CompletableFuture<Integer> task2 = CompletableFuture.supplyAsync(() -> {
            System.out.println("异步任务2,当前线程是:" + Thread.currentThread().getId());
            int result = 1 + 1;
            System.out.println("异步任务2结束");
            return result;
        }, executorService);

        task.runAfterBothAsync(task2, () -> {
            System.out.println("runAfterBoth 无入参无返回值,当前线程是:" + Thread.currentThread().getId());
        },executorService);

        task.thenAcceptBothAsync(task2, (t1, t2) -> {
            System.out.printf("runAfterBoth 有入参:task1入参(%s)task2入参(%s)无返回值,当前线程是:%s%n", t1, t2, Thread.currentThread().getId());
        }, executorService);
        //任务组合
        CompletableFuture<Integer> task3 = task.thenCombineAsync(task2, (t1, t2) -> {
            System.out.printf("thenCombineAsync 有入参:task1入参(%s)task2入参(%s)有返回值,当前线程是:%s%n", t1, t2, Thread.currentThread().getId());
            System.out.println("任务1返回值:" + t1);
            System.out.println("任务2返回值:" + t2);
            return t1 + t2;
        }, executorService);

        Integer res = task3.get();
        System.out.println("最终结果:" + res);
    }
输出结果
异步任务1,当前线程是:12
异步任务1结束
异步任务2,当前线程是:13
异步任务2结束
runAfterBoth 无入参无返回值,当前线程是:14
runAfterBoth 有入参:task1入参(2)task2入参(2)无返回值,当前线程是:15
thenCombineAsync 有入参:task1入参(2)task2入参(2)有返回值,当前线程是:16
任务1返回值:2
任务2返回值:2
最终结果:4
OR 组合关系

applyToEither / acceptEither / runAfterEither 都表示:「两个任务,只要有一个任务完成,就执行任务三」

区别在于

  • runAfterEither:不会把执行结果当做方法入参,且没有返回值
  • acceptEither: 会将已经执行完成的任务,作为方法入参,传递到指定方法中,且无返回值
  • applyToEither:会将已经执行完成的任务,作为方法入参,传递到指定方法中,且有返回值
示例代码
	@Test
    public void testCompletableEitherAsync() throws ExecutionException, InterruptedException {
        //创建线程池
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        //开启异步任务1
        CompletableFuture<Integer> task = CompletableFuture.supplyAsync(() -> {
            System.out.println("异步任务1,当前线程是:" + Thread.currentThread().getId());

            int result = 1 + 1;
            System.out.println("异步任务1结束");
            return result;
        }, executorService);

        //开启异步任务2
        CompletableFuture<Integer> task2 = CompletableFuture.supplyAsync(() -> {
            System.out.println("异步任务2,当前线程是:" + Thread.currentThread().getId());
            int result = 1 + 2;
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("异步任务2结束");
            return result;
        }, executorService);

        //任务组合
        task.runAfterEitherAsync(task2, () -> {
            System.out.println("执行任务3,当前线程是:" + Thread.currentThread().getId());
        }, executorService);
        // 不通过 get 阻塞的话主线程不会等待任务2执行完
        System.out.println(task2.get());
    }
运行结果
异步任务1,当前线程是:12
异步任务1结束
异步任务2,当前线程是:13
执行任务3,当前线程是:14

多任务组合

allOf:等待所有完成;

anyOf:只要有一个任务完成。

allOf

	@Test
    public void testCompletableAallOf() throws ExecutionException, InterruptedException {
        //创建线程池
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        //开启异步任务1
        CompletableFuture<Integer> task = CompletableFuture.supplyAsync(() -> {
            System.out.println("异步任务1,当前线程是:" + Thread.currentThread().getId());
            int result = 1 + 1;
            System.out.println("异步任务1结束");
            return result;
        }, executorService);

        //开启异步任务2
        CompletableFuture<Integer> task2 = CompletableFuture.supplyAsync(() -> {
            System.out.println("异步任务2,当前线程是:" + Thread.currentThread().getId());
            int result = 1 + 2;
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("异步任务2结束");
            return result;
        }, executorService);

        //开启异步任务3
        CompletableFuture<Integer> task3 = CompletableFuture.supplyAsync(() -> {
            System.out.println("异步任务3,当前线程是:" + Thread.currentThread().getId());
            int result = 1 + 3;
            try {
                Thread.sleep(4000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("异步任务3结束");
            return result;
        }, executorService);

        //任务组合
        CompletableFuture<Void> allOf = CompletableFuture.allOf(task, task2, task3);

        allOf.thenRun(() -> {
            System.out.println("大家都执行完了我再执行!");
        });
        
        //等待所有任务完成
        allOf.get();
        //获取任务的返回结果
        System.out.println("task结果为:" + task.get());
        System.out.println("task2结果为:" + task2.get());
        System.out.println("task3结果为:" + task3.get());
    }
使用 allOf 的输出结果
异步任务1,当前线程是:12
异步任务1结束
异步任务2,当前线程是:13
异步任务3,当前线程是:14
异步任务2结束
异步任务3结束
大家都执行完了我再执行!
task结果为:2
task2结果为:3
task3结果为:4
注释掉 allOf 的输出结果
异步任务1,当前线程是:12
异步任务1结束
异步任务2,当前线程是:13
task结果为:2
异步任务3,当前线程是:14
异步任务2结束
task2结果为:3
异步任务3结束
task3结果为:4

anyOf

	@Test
    public void testCompletableAnyOf() throws ExecutionException, InterruptedException {
        //创建线程池
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        //开启异步任务1
        CompletableFuture <String> task = CompletableFuture.supplyAsync(() -> {
            return "task1";
        }, executorService);

        //开启异步任务2
        CompletableFuture <String> task2 = CompletableFuture.supplyAsync(() -> {
            return "task2";
        }, executorService);

        //开启异步任务3
        CompletableFuture <String> task3 = CompletableFuture.supplyAsync(() -> {
            return "task3";
        }, executorService);

        //任务组合
        CompletableFuture<Object> anyOf = CompletableFuture.anyOf(task, task2, task3);

        anyOf.thenAcceptAsync((v) -> {
            System.out.printf("%s 执行完成了,我也可以执行了!", v);
        });
    }
输出结果
task1 执行完成了,我也可以执行了!

CompletableFuture 使用有哪些注意点

Future需要获取返回值,才能获取异常信息

Future需要获取返回值,才能获取到异常信息。如果不加 get()/join()方法,看不到异常信息。

小伙伴们使用的时候,注意一下哈,考虑是否加try...catch...或者使用exceptionally方法。

CompletableFuture的get()方法是阻塞的

CompletableFuture的get()方法是阻塞的,如果使用它来获取异步调用的返回值,需要添加超时时间。

不建议使用默认线程池

CompletableFuture代码中又使用了默认的「ForkJoin线程池」,处理的线程个数是电脑「CPU核数-1」。在大量请求过来的时候,处理逻辑复杂的话,响应会很慢。一般建议使用自定义线程池,优化线程池配置参数。

还有就是「ForkJoin线程池」会将所有的线程都设置成守护线程,所有所有的用户线程执行完成后, JVM 也就退出了。

ForkJoin 源码

final WorkQueue registerWorker(ForkJoinWorkerThread wt) {
        UncaughtExceptionHandler handler;
        wt.setDaemon(true);                           // configure thread
        if ((handler = ueh) != null)
            wt.setUncaughtExceptionHandler(handler);
        WorkQueue w = new WorkQueue(this, wt);
        .....
}

自定义线程池时,注意饱和策略

CompletableFuture的get()方法是阻塞的,我们一般建议使用future.get(5, TimeUnit.SECONDS)。并且一般建议使用自定义线程池。

但是如果线程池拒绝策略是DiscardPolicy或者DiscardOldestPolicy,当线程池饱和时,会直接丢弃任务,不会抛弃异常。因此建议,CompletableFuture线程池策略最好使用AbortPolicy,然后耗时的异步线程,做好线程池隔离哈。

引用

CompletableFuture实践

奇淫巧技,CompletableFuture 异步多线程是真的优雅

把之前CompletableFuture留下的坑给填上。

posted @ 2022-09-03 10:44  李小龙他哥  阅读(141)  评论(0编辑  收藏  举报