Java8特性 - CompletableFuture的基础使用

一. CompletableFuture的简单介绍

CompletableFuture是Java8里面推出的新特性,其实现了FutureCompletionStage接口,可以表示一个异步计算的结果。通过调用get()方法进入阻塞直到结果返回。

对于原生的Future类,我们只能通过轮询或者阻塞的方式来获取其结果,而不断的轮询又会消耗CPU,同时只能在获得结果之后手动做逻辑处理。而CompletableFuture拓展了Future,简化异步编程的复杂性,提供了函数式编程的能力,可以通过回调的方式处理计算结果,并且提供了各种转换以及组合的相关API。

CompletableFuture去执行一个异步代码,有两个最常见的方法:

  • supplyAsync:异步、有返回值。
  • runAsync:异步:无返回值(Void类型)(因为接收的参数是一个Runnable)

先来一个简单的异步Demo,了解下CompletableFuture是怎么使用的:

@Test
public void test() throws Exception {
    long start = System.currentTimeMillis();
    // 有返回值
    CompletableFuture<String> c = CompletableFuture.supplyAsync(() -> {
        try {
            TimeUnit.SECONDS.sleep(2);
            System.out.println("-----supplyAsync-----");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "Hello";
    });

    // 无返回值
    CompletableFuture<Void> c2 = CompletableFuture.runAsync(() -> {
        try {
            TimeUnit.SECONDS.sleep(3);
            System.out.println("-----runAsync-----");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    });
    // 阻塞等待所有异步任务结束
    CompletableFuture.allOf(c, c2).get();
    long end = System.currentTimeMillis();
    System.out.println("主线程执行结束,消耗时长:" + (end - start) + "ms");
}

最终程序的执行结果如下,可见程序并不是串行的等待了5秒,而是通过异步的方式,等待了3秒(消耗时间最长的任务)。
在这里插入图片描述
上述两种方法,都有其对应的重载方法,可以让我们手动传入一个线程池,用于创建执行异步任务的线程,以runAsync为例:

public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor) {
   return asyncRunStage(screenExecutor(executor), runnable);
}

1.1 主线程和守护线程

CompletableFuture在默认的情况下(不传入指定线程池),异步任务采用的线程是通过ForkJoinPool创建出来的子线程,是守护线程。
验证如下:

 @Test
 public void test2() throws Exception {
     CompletableFuture<String> c = CompletableFuture.supplyAsync(() -> {
         System.out.println("supplyAsync" + (Thread.currentThread().isDaemon() ? "是" : "不是") + "守护线程");
         try {
             TimeUnit.SECONDS.sleep(2);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
         System.out.println("supplyAsync执行结束");
         return "Hello";
     });
     c.get();
     System.out.println("主线程执行结束");
 }

结果如下:
在这里插入图片描述
守护线程:

Java的线程分为两种:用户线程、守护线程。默认情况下创建出来的线程都是用户线程。守护线程的优先级比较低,用于为系统中的其它对象和线程提供服务,例如我们Java自带的GC功能,就是通过守护线程来执行的。

需要注意的点:

  1. 守护线程会等到主线程执行完毕后也跟着结束。
  2. 对主线程来说, 运行完毕指的是主线程所在的进程内所有非守护线程统统运行完毕, 主线程才算运行完毕。

那么问题随之而来,加入我把上述代码改为:

@Test
public void test3() throws Exception {
    long start = System.currentTimeMillis();
    // 有返回值
    CompletableFuture<String> c = CompletableFuture.supplyAsync(() -> {
        try {
            TimeUnit.SECONDS.sleep(2);
            System.out.println("-----supplyAsync-----");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "Hello";
    });

    // 无返回值
    CompletableFuture<Void> c2 = CompletableFuture.runAsync(() -> {
        try {
            TimeUnit.SECONDS.sleep(3);
            System.out.println("-----runAsync-----");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    });
    long end = System.currentTimeMillis();
    System.out.println("主线程执行结束,消耗时长:" + (end - start) + "ms");
}

结果将会是:
在这里插入图片描述
很明显,可以发现,我两个异步任务里面的输出语句都没有执行到,而对于主线程而言,执行了61ms就结束了,那么主线程关闭,CompletableFuture通过默认方式创建的异步任务(非守护线程)也会随之结束,哪怕没有执行完毕。

因此想要解决这样的问题,就应该让主线程进入阻塞状态,等待守护线程全部执行完毕,也就是get()方法。或者保证执行异步任务的期间,主线程内还有非守护线程还在执行。或是传入自己指定的线程池,来代替默认的ForkJoinPoll
例如:

@Test
public void test3() throws Exception {
    long start = System.currentTimeMillis();
    // 有返回值
    CompletableFuture<String> c = CompletableFuture.supplyAsync(() -> {
        try {
            TimeUnit.SECONDS.sleep(2);
            System.out.println("-----supplyAsync-----");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "Hello";
    });

    // 无返回值
    CompletableFuture<Void> c2 = CompletableFuture.runAsync(() -> {
        try {
            TimeUnit.SECONDS.sleep(3);
            System.out.println("-----runAsync-----");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    });
    // 一般我们创建出来的线程都是用户线程
    ExecutorService executorService = Executors.newFixedThreadPool(10);
    executorService.submit(()->{
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }).get();
    long end = System.currentTimeMillis();
    System.out.println("主线程执行结束,消耗时长:" + (end - start) + "ms");
}

执行结果如下,可以见到异步任务中的输出都有了。
在这里插入图片描述

二. 基础的运算

2.1 thenAccept

thenAccept:当原来的CompletableFuture计算完后,将结果传递给函数fn,将fn的结果作为新的CompletableFuture并计算新的结果。案例如下:

@Test
public void test4() {
    CompletableFuture<String> c1 = CompletableFuture.supplyAsync(() -> {
        System.out.println("今天天气不错");
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "衣服";
    });
    CompletableFuture<Void> end = c1.thenAccept(data -> {
        System.out.println("我去买了" + data);
    });
    end.join();
}

结果:
在这里插入图片描述

2.2 whenComplete

当计算完成或者抛出异常后,我们可以调用whenComplete来执行指定的动作。
案例如下:

@Test
public void test4() {
    CompletableFuture<String> c1 = CompletableFuture.supplyAsync(() -> {
        System.out.println("今天天气不错");
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "衣服";
    });
    CompletableFuture<String> end = c1.whenComplete((pre, cur) -> {
        System.out.println("我去买了" + pre);
    });
    end.join();
}

结果和thenAccept一致,但是两者区别如下:

  • thenAccept:无返回值(Void),返回一个新的CompletableFuture
  • whenComplete:有返回值,返回的CompletableFuture的返回与原有返回结果的是同一个(已做修改,原表述错误,感谢评论区turbo强)。

(增强案例)证明如下:

@org.junit.Test
public void test4() throws ExecutionException, InterruptedException {
    CompletableFuture<String> c1 = CompletableFuture.supplyAsync(() -> {
        System.out.println("今天天气不错");
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "衣服";
    });
    CompletableFuture<String> c2 = c1.whenComplete((pre, cur) -> {
        System.out.println("我去买了" + pre);
    });
    c2.join();
    System.out.println("Future1的结果:" + c1.get());
    System.out.println("Future2的结果:" + c2.get());
}

结果如下:
在这里插入图片描述

2.3 thenCompose

thenCompose是用于组合CompletableFuture,并返回一个新的CompletableFuture,案例如下:

@Test
public void test4() throws ExecutionException, InterruptedException {
    CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
        return 100;
    });
    CompletableFuture<String> f =  future.thenCompose( i -> {
        return CompletableFuture.supplyAsync(() -> {
            return (i * 10) + "";
        });
    });
    System.out.println(f.get());
}

返回结果:
在这里插入图片描述

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Zong_0915

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值