CompletableFuture

参考

CompletableFuture使用详解

CompletableFuture使用详解(超详细)

【深度解析】万字长文,一文讲透CompletableFuture:Java异步编程的利器!

test_futureTask

public class Test002 {

    public static Logger log = LoggerFactory.getLogger(Test002.class);

    public static void main(String[] args) {

        test_futureTask();      
    }


    private static void test_futureTask() {

        // 1. Runnable没有返回值且无声明异常: run():void
        //    Callable有返回值且有声明异常:   call() throws Exception():V

        // 2. Runnable能够被线程所执行, Callable不能够被线程所执行

        // 3 Future接口定义了与异步任务相关的方法
        //     cancel(boolean):boolean
        //     get():V  throws InterruptedException, ExecutionException 
        //        如果任务中发生异常, 此方法将会抛出ExecutionException
        //        如果任务正在执行, 尚未执行完, 其它线程调用FutureTask的cancel(true)方法, 此方法将抛出CancelException
        //     get(long, TimeUnit):V throws InterruptedException, ExecutionException, TimeoutException
        //        如果任务中发生异常, 此方法将会抛出ExecutionException
        //        如果任务正在执行, 尚未执行完, 其它线程调用FutureTask的cancel(true)方法, 此方法将抛出CancelException
        //        如果超时未获取到结果, 此方法将抛出异常
        //     isCancelled():boolean
        //     isDone():boolean

        // 4. RunnableFuture接口继承了 Runnable接口 和 Future接口, 而 FutureTask实现了 RunnableFuture接口,
        //    所以FutureTask还需要传入1个Callable, 才能同时满足 多线程、异步接口、有返回值


        Callable<String> callable = () -> {

            log.info("come in===>");

            return "ok";
        };


        FutureTask<String> futureTask = new FutureTask<String>(callable);

        Thread t1 = new Thread(futureTask, "t1");

        t1.start();
    }

}

test_futureTaskWithThreadPool

package cn.itcast.redisdemo;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.*;

public class Test002 {

    public static Logger log = LoggerFactory.getLogger(Test002.class);

    public static void main(String[] args) {

        test_futureTaskWithThreadPool();

    }

    private static void test_futureTaskWithThreadPool() {

        ExecutorService threadPool = Executors.newFixedThreadPool(3);

        log.info("start...");

        // FutureTask是Runnable(可以被线程执行)和Future(可以获取异步结果)的组合
        // 而传入的Callable的call()方法则是封装了产生结果的逻辑
        FutureTask<String> futureTask1 = new FutureTask<String>(()->{
            TimeUnit.SECONDS.sleep(1);
            return "halo1";
        });

        FutureTask<String> futureTask2 = new FutureTask<String>(()->{
            TimeUnit.SECONDS.sleep(3);
            return "halo2";
        });

        FutureTask<String> futureTask3 = new FutureTask<String>(()->{
            TimeUnit.SECONDS.sleep(2);
            return "halo3";
        });

        threadPool.submit(futureTask1);
        threadPool.submit(futureTask2);
        threadPool.submit(futureTask3);

        // 提交1个Callable给线程池, 实际上会把这个Callable传给1个新创建的FutureTask, 并将此FutureTask返回
        Callable callable = new Callable() {
            @Override
            public Object call() throws Exception {
                TimeUnit.SECONDS.sleep(2);
                return "call";
            }
        };
        Future futureTask4 = threadPool.submit(callable);

        // 提交1个Runnable给线程池, 但是通过前面我们知道提交给线程池的会被封装为FutureTask, 而FutureTask需要的是1个Callable,
        // 所以这里使用了适配器模式 RunnableAdapter实现了Callable接口, 当执行完run方法后, 直接将此处传入的结果返回
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        Future futureTask5 = threadPool.submit(runnable, "run");


        try {
            futureTask1.get();
            futureTask2.get();
            futureTask3.get();

            futureTask4.get();
            futureTask5.get();

        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }

        log.info("end");


    }


}

FutureTask的缺点

  1. 对于简单的业务场景使用Future完全OK
  2. 回调通知
    • 应对Future的完成时间, 完成了可以告诉我, 也就是我们的回调通知
    • 通过轮询的方式去判断任务是否完成这样非常占用CPU并且代码不优雅
  3. 创建异步任务
    • Future + 线程池配合
  4. 多个任务前后依赖可以组合处理
    • 想将多个异步任务的计算结果组合起来,后一个异步任务的计算结果需要前一个异步任务的值
    • 将两个或多个异步计算合成一个异步计算,这几个异步计算五相独立,同时后面这个又依赖前一个处理的结果
  5. 对计算速度选最快
    • 当Future集合中某个任务最快结束时,返回结果,返回第一名处理结果

FutureTask的缺点: Future对于结果的获取不是很友好, 只能通过阻塞(future.get())或轮询(while(true) + future.isDone())的方式获得任务的结果

  1. get()方法在Future 计算完成之前会一直处在阻塞状态下
  2. isDone()方法容易耗费CPU资源
  3. 对于真正的异步处理我们希望是可以通过传入回调函数,在Future结束时自动调用该回调函数,这样,我们就不用等待结果。

阻塞的方式和异步编程的设计理念相违背,而轮询的方式会耗费无谓的CPU资源。因此,JDK8设计出CompletableFuture。

CompletableFuture提供了一种观察者模式类似的机制,可以让任务执行完成后通知监听的一方

CompletableFuture

CompletableFuture实现了 CompletionStage接口 和 Future接口

CompletionStage接口

代表异步计算过程中的某一个阶段,一个阶段完成以后可能会触发另外一个阶段,有些类似Linux系统的管道分隔符传参数

  1. CompletionStage代表异步计算过程中的某一个阶段,一个阶段完成以后可能会触发另外一个阶段
  2. 阶段的计算执行可以是一个Function, Consumer或者Runnable,
    比如: stage.thenApply(x -> square(x))
    .thenAccept(x-> System.out.print(x))
    .thenRun(() -> System.out.println())
  3. 一个阶段的执行可能是被单个阶段的完成触发,也可能是由多个阶段一起触发

CompletableFuture类

  1. 在Java8中,CompletableFuture提供了非常涯大的Future的扩展功能,可以帮助我们简化异步编程的复杂性,并且提供了函数式编程的能力,可以通过回调的方式处理计算结果,也提供了转换和组合
  2. CompletableFuture 的方法它可能代表一个明确完成的Future, 也有可能代表一个完成阶段 ( CompletionSstage),它支持在计算完成以后触发一些函数或执行某些动作。
  3. 它实现了Future和CompletionStage接口

CompletableFuture的4个静态方法

  1. runAsync无返回值
    public static CompletableFuture<Void> runAsync(Runnable runnable)
    public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)
  2. supplyAsync有返回值
    public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
    public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)

executor参数说明

  • 没有指定Executor的方法,直接使用默认的ForkJoinPool.commonPool()作为它的线程池执行异步代码。
  • 如果指定线程池,则使用我们自定义的或者特别指定的线程池执行异步代码

无返回值示例

ExecutorService threadPool = Executors.newFixedThreadPool(3);

// 一提交, 就开始异步执行了
CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> {
    try {
        log.info("runAsync无返回值");
        TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}, threadPool);


System.out.println(completableFuture.get());

有返回值示例

ExecutorService threadPool = Executors.newFixedThreadPool(3);

// 一提交, 就开始异步执行了
CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
    try {
        log.info("supplyAsync有返回值");
        TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return "ok~";
}, threadPool);

// 也可以调用completableFuture.join()来获取结果
System.out.println(completableFuture.get());

Completable减少阻塞和轮询,自动回调

  • 从Java8开始引入了CompletableFuture,它是Future的功能增强版,减少阻塞和轮询
  • 可以传入回调对象,当异步任务完成或者发生异常时,自动调用回调对象的回调方注
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
    log.info("enter...");
    try {
        TimeUnit.SECONDS.sleep(2);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    if (true) throw new RuntimeException("异常1");
    return new Random().nextInt(10);
}).whenComplete((result, throwable) -> {

    // 1. 不管上一步的任务是否发生异常, 这里都会走
    // 2. 这个whenComplete的返回值也是个CompletableFuture,
    //       如果上一步任务发生了异常, 那么whenComplete返回的CompletableFuture就是个失败的CompletableFuture, 所以后面如果再接exceptionally就会走exceptionally的逻辑
    //       如果上一步任务没有发生异常, 那么whenComplete返回的CompletableFuture就是个成功的CompletableFuture, 所以后面如果再接exceptionally就不会走exceptionally的逻辑
    //    总结来说就是这个whenComplete不会影响上个任务的CompletableFuture的状态
    // 3. 获取的result是上个任务的结果, throwable是上个任务的异常(如果有的话)
    // 4. 这里的whenComplete是去消费结果, 是上一步做完了(正常执行完获得结果而结束 或者是 发生了异常而结束), 再去处理这个做完的结果

    log.info("任务完成");

    // if (true) throw new RuntimeException("异常2");

    if (throwable == null) {
        log.info("产生的随机数是: {}", result);
    }


}).exceptionally(throwable -> {

    // 1. 如果发生了异常, 这里才会走(可以跟js的Promise一起理解, 1个CompletableFuture就是1个Promise, 就看这个CompletableFuture是个什么状态)
    // 2. 如果调用此exceptionally方法的CompletableFuture是失败的, 那么会走exceptionally中定义的逻辑,
    //    而exceptionally定义的方法返回的是1个新的CompletableFuture, 这个新的CompletableFuture状态与方法的逻辑有关
    log.info("任务发生异常:{}, 返回1个默认的值", throwable.getMessage());

	// 这里如果抛出异常, 当前的这个exceptionally后面再接exceptionally, 则后面的这个exceptinally收到的异常就是这里抛出来的异常了
    // if (true) throw new RuntimeException("异常3");

    return 100;

}); // 这里后面还可以继续接whenComplete和exceptionally

log.info("main...");

// 也可以通过CompletableFuture来获取结果
log.info("获取到最终的结果: {}", completableFuture.get());

// 主线程不要立刻结束,否则CompletableFuture默认使用的线程池会立刻关闭
LockSupport.park();


// CompletableFuture的优点
// 1. 异步任务结束时,会自动回调某个对象的方法;
// 2. 主线程设置好回调后,不再关心异步任务的执行,
// 3. 异步任务之间可以顺序执行异步任务出错时,会自动回调某个对象的方法;

CompletableFuture常用方法

1.获得结果和触发计算

@Slf4j
public class Test004 {

    public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
        CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {

            log.info("enter...");

            //暂停几秒钟线程
            try {
                TimeUnit.SECONDS.sleep(1);
                if(true) throw new RuntimeException("异常1");
            } catch (Exception e) {
                e.printStackTrace();
            }

            log.info("ending...");

            return "abc";

        });

        // 一直等到有结果为止, 或者任务抛出异常
        // System.out.println(completableFuture.get());

        // 一直等到有结果为止, 或者任务抛出异常, 或者因超时而抛出超时异常
        // System.out.println(completableFuture.get(2L, TimeUnit.SECONDS));

        // 一直等到有结果为止, 或者任务抛出异常
        // System.out.println(completableFuture.join());

        // 立即拿结果不阻塞, 如果没有结果则返回给定值
        // System.out.println(completableFuture.getNow("xxx"));

        // TimeUnit.MILLISECONDS.sleep(100);

        try {
            TimeUnit.MILLISECONDS.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 尝试使用指定的值作为任务结果来结束任务, 返回值表示当前结束任务操作是否成功了
        // (如果任务尚未完成, 任务会继续执行, 但这个任务已经有结果了, 即使后面任务完成也不会修改结果)
        boolean triggered = completableFuture.complete("completedVal");

        log.info("trigger: {}", triggered);

        log.info("结果: {}", completableFuture.get());

        LockSupport.park();
    }
}

2.对计算结果进行处理

thenApply:计算结果存在依赖关系,使得这两个线程串行化(中间一步有异常时,不走下一步)

handle:计算结果存在依赖关系,使得这两个线程串行化(中间一步有异常时,接着往下处理)

3.对计算结果进行消费

thenAccept:接收上一步任务的处理结果,并消费处理,当前任务无返回结果

注意区别:

  • thenApply(需要上一步任务的结果,并且当前任务有返回结果)
  • thenRun(不需要上一步任务的结果)

异步线程池:

  1. 没有传入自定义线程池,都用默认线程池ForkJoinPool:
  2. 传入了一个自定义线程池的情况
    • 如果你执行第一个任务的时候,传入了一个自定义线程池:
      • 调用thenRun方法执行第二个任务时,则第二个任务和第一个任务是共用同一个线程池。
      • 调用thenRunAsync执行第二个时,则第一个任务使用的是你自己传入的线程池,第二个任务使用的是ForkJoin线程池

4.对计算速度进行选用

谁快用谁:applyEither

5.对计算结果进行合并

thenCombine

  • 两个CompletionStage任务都完成后,最终能把两个任务的结果一起交给thenCombine来处理
  • 先完成的先等着,等待其它分支任务
  • 5
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值