【JUC】CompletableFuture

文章目录

    • 1. Future接口
    • 2. CompletableFuture的出现
    • 3. CompletableFuture四大静态方法
    • 4.使用演示
    • 5. 常用方法
      • 5.1 获得结果和触发计算
      • 5.2 对计算结果进行处理
      • 5.3 对计算结果进行消费
      • 5.4 对计算速度进行选用
      • 5.5 对计算结果进行合并

1. Future接口

  • Future接口(FutureTask实现类)定义了操作异步任务执行一些方法,如获取异步任务的执行结果、取消任务的执行、判断任务是否被取消、判断任务执行是否完毕等
  • 比如主线程让一个子线程去执行任务,子线程可能比较耗时,启动子线程开始执行任务后,主线程就去做其他事情了,忙其它事情或者先执行完,过了一会才去获取子任务的执行结果或变更的任务状态
  • 三个特点:
    • 多线程
    • 有返回
    • 异步任务
  • FutureTask基本使用:
    public static void main(String[] args) throws ExecutionException, InterruptedException {

        FutureTask<String> futureTask = new FutureTask<>(() ->{
            System.out.println("我进来了");
            return "老六是我";
        });
        Thread t1 = new Thread(futureTask, "t1");
        t1.start();
        System.out.println(futureTask.get());
    }
  • FutureTask缺点:
    • get()阻塞,一旦调用get()方法求结果,如果计算没有完成容易导致程序阻塞(可设置超时时间,超时会抛出超时异常)
    • isDone()轮询
      • 轮询的方式会耗费无谓的CPU资源,而且也不见得能及时地得到计算结果
      • 如果想要异步获得结果,通常都会以轮询的方式去获取结果,尽量不要阻塞
    • 结论:Future对于结果的获取不是很友好,只能通过阻塞或轮询的方式得到任务的结果

2. CompletableFuture的出现

  • get()方法在Future 计算完成之前会一直处在阻寒状态下

  • isDone()方法容易耗费CPU资源

  • 对于真正的异步处理我们希望是可以通过传入回调函数,在Future结束时自动调用该回调函数,这样,我们就不用等待结果

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

    因此,JDK8设计出CompletableFuture

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

3. CompletableFuture四大静态方法

public static CompletableFuture<Void> runAsync(Runnable runnable)
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)

参数说明:

  • 没有指定executor的方法,直接使用默认的ForkJoinPool.commonPool()作为它的线程池执行异步代码
  • 指定线程池则使用指定的线程池执行代码

4.使用演示

它是Future的功能增强版,减少阻塞和轮询,可以传入回调对象,当异步任务完成或者发生异常时,自动调用回调对象的回调方法

    public static void main(String[] args) {
        CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName());
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
            System.out.println(1/0);
            return "卧槽,外挂";
        }).whenComplete((res, e) -> {
            if (e != null) {
                System.out.println("有异常");
                e.printStackTrace();
            } else {
                System.out.println("计算结果:" + res);
            }
        }).exceptionally(e -> {
            System.out.println("出异常了");
            e.printStackTrace();
            return null;
        });

        System.out.println("主线程:"+ Thread.currentThread().getName());
        try {
            // ForkJoinPool线程池是守护线程,必须要保持存在用户线程
            TimeUnit.SECONDS.sleep(2);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

运行结果

ForkJoinPool.commonPool-worker-9
主线程:main
有异常
出异常了
java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
	at java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:273)
	at java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:280)
	at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1606)
	at java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1596)
	at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
	at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1067)
	at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1703)
	at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:172)
Caused by: java.lang.ArithmeticException: / by zero
	at Main.lambda$main$0(Main.java:20)
	at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1604)
	... 5 more
java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
	at java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:273)
	at java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:280)
	at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1606)
	at java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1596)
	at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
	at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1067)
	at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1703)
	at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:172)
Caused by: java.lang.ArithmeticException: / by zero
	at Main.lambda$main$0(Main.java:20)
	at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1604)
	... 5 more

Process finished with exit code 0

5. 常用方法

以下方法带Async的对应方法会使用默认线程池ForkJoinPool进行线程管理

5.1 获得结果和触发计算

public T get() // 阻塞
public T get(long timeout, TimeUnit unit) // 阻塞,超时则抛出异常
public T join() // 阻塞,代码中无需抛出编译异常
public T getNow(T valueIfAbsent) // 获得当前计算结果,如线程已经计算完成则返回计算结果;如当前未计算出结果则返回valueIfAbsent;不会打断线程的执行
public boolean complete(T value) // 返回值表示是否有打断get方法,有打断则后续join获取到的为括号内的值,未打断则获取到的是线程执行完的结果值

5.2 对计算结果进行处理

public <U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn) // 计算结果存在依赖关系,线程串行化执行;有异常不执行下一步
public <U> CompletableFuture<U> handle(BiFunction<? super T, Throwable, ? extends U> fn) // 同上,但是有异常时会带着异常到下一步

5.3 对计算结果进行消费

public CompletableFuture<Void> thenAccept(Consumer<? super T> action) // 接收任务的处理结果,并消费处理,无返回结果

5.4 对计算速度进行选用

// 调用该方法的返回值的join会返回速度执行快的线程的执行结果,同时执行慢的线程也会继续执行而不会中断
public <U> CompletableFuture<U> applyToEither(
        CompletionStage<? extends T> other, Function<? super T, U> fn) 

5.5 对计算结果进行合并

// 两个CompletionStage任务都完成后,最终把两个任务的结果一起交给thenCombine来处理,先完成的先等着,等待其它分支任务
public <U,V> CompletableFuture<V> thenCombine(
    CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn)

你可能感兴趣的:(#,03,JUC,java)