多线程系列(二十) -CompletableFuture使用详解

一、摘要

在上篇文章中,我们介绍了Future相关的用法,使用它可以获取异步任务执行的返回值。

我们再次回顾一下Future相关的用法。

public class FutureTest {
   

    public static void main(String[] args) throws Exception {
   
        long startTime = System.currentTimeMillis();
        // 创建一个线程池
        ExecutorService executor = Executors.newFixedThreadPool(1);

        // 提交任务并获得Future的实例
        Future<String> future = executor.submit(new Callable<String>() {
   
            @Override
            public String call() throws Exception {
   
                // 执行下载某文件任务,并返回文件名称
                System.out.println("thread name:" +  Thread.currentThread().getName() + " 开始执行下载任务");
                Thread.sleep(200);
                return "xxx.png";
            }
        });

        //模拟主线程其它操作耗时
        Thread.sleep(300);

        // 通过阻塞方式,从Future中获取异步执行返回的结果
        String result = future.get();
        System.out.println("任务执行结果:" +  result);
        System.out.println("总共用时:" + (System.currentTimeMillis() - startTime) + "ms");

        // 任务执行完毕之后,关闭线程池
        executor.shutdown();
    }
}

运行结果如下:

thread name:pool-1-thread-1 开始执行下载任务
任务执行结果:xxx.png
总共用时:308ms

如果不采用线程执行,那么总共用时应该会是 200 + 300 = 500 ms,而采用线程来异步执行,总共用时是 308 ms。不难发现,通过Future和线程池的搭配使用,可以有效的提升程序的执行效率。

但是Future对异步执行结果的获取并不是很友好,要么调用阻塞方法get()获取结果,要么轮训调用isDone()方法是否等于true来判断任务是否执行完毕来获取结果,这两种方法都不算很好,因为主线程会被迫等待。

因此,从 Java 8 开始引入了CompletableFuture,它针对Future做了很多的改进,在实现Future接口相关功能之外,还支持传入回调对象,当异步任务完成或者发生异常时,自动调用回调对象方法。

下面我们一起来看看CompletableFuture相关的用法!

二、CompletableFuture 用法介绍

我们还是以上面的例子为例,改用CompletableFuture来实现,内容如下:

public class FutureTest2 {
   

    public static void main(String[] args) throws Exception {
   
        // 创建异步执行任务
        CompletableFuture<String> cf = CompletableFuture.supplyAsync(FutureTest2::download);

        // 如果执行成功,回调此方法
        cf.thenAccept((result) -> {
   
            System.out.println("任务执行成功,返回结果值:" +  result);
        });

        // 如果执行异常,回调此方法
        cf.exceptionally((e) -> {
   
            System.out.println("任务执行失败,原因:" +  e.getMessage());
            return null;
        });

        //模拟主线程其它操作耗时
        Thread.sleep(300);
    }

    /**
     * 下载某个任务
     * @return
     */
    private static String download(){
   
        // 执行下载某文件任务,并返回文件名称
        System.out.println("thread name:" +  Thread.currentThread().getName() + " 开始执行下载任务");
        try {
   
            Thread.sleep(200);
        } catch (InterruptedException e) {
   
            e.printStackTrace();
        }
        return "xxx.png";
    }
}

运行结果如下:

thread name:ForkJoinPool.commonPool-worker-1 开始执行下载任务
任务执行成功,返回结果值:xxx.png

可以发现,采用CompletableFuture类的supplyAsync()方法进行异步编程,代码上简洁了很多,不需要单独创建线程池。

实际上,CompletableFuture也使用了线程池来执行任务,部分核心源码如下:

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

    // 判断当前机器 cpu 可用逻辑核心数是否大于1
    private static final boolean useCommonPool = (ForkJoinPool.getCommonPoolParallelism() > 1);
    
    // 默认采用的线程池
    // 如果useCommonPool = true,采用 ForkJoinPool.commonPool 线程池
    // 如果useCommonPool = false,采用 ThreadPerTaskExecutor 执行器
    private static final Executor asyncPool = useCommonPool ?
        ForkJoinPool.commonPool() : new ThreadPerTaskExecutor();

    // ThreadPerTaskExecutor执行器类
    static final class ThreadPerTaskExecutor implements Executor {
   
        public void execute(Runnable r) {
    new Thread(r).start(); }
    }


    // 异步执行任务的方法
    public static <U> CompletableFuture<U> supplyAsync(Supplier
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值