Future CompleteFuture

news/2024/7/17 14:04:47

文章目录

  • 前言
  • 1、Callable、Future、FutureTask
    • 1.1 Callable 和 Runnable 的区别
    • 1.2、使用案例
    • 1.3、Future的局限性
    • 1.4、Future 注意事项
  • 2、CompletionService
    • 2.1、CompletionService 原理
    • 2.2、使用案例
    • 2.3、应用场景总结
  • 3、CompletableFuture
    • 3.1、创建异步操作
      • 3.1.1、runAsync
      • 3.1.2、supplyAsync
    • 3.2、获取结果
      • 3.2.1、get & join
    • 3.3、结果处理
    • 3.3.1、whenComplete & exceptionally
    • 3.4、结果转换
      • 3.4.1、thenApply
      • 3.4.2、thenCompose
    • 3.5、结果消费
      • 3.5.1、thenAccept
      • 3.5.2、thenAcceptBoth
      • 3.5.3、thenRun
    • 3.6、结果组合
      • 3.6.1、thenCombine
    • 3.7、任务交互
      • 3.7.1、applyToEither
      • 3.7.2、acceptEither
      • 3.7.3、runAfterEither
      • 3.7.4、runAfterBoth
      • 3.7.5、anyOf
      • 3.7.6、allOf
  • 4、CompletableFuture 常用方法总结
  • 5、Java8 CompleteFuture 简单使用
  • 6、实现最优的 烧水泡茶 程序
    • 6.1、基于Future实现
    • 6.2、基于CompletableFuture实现
  • 7、demo

前言

Java8 中的 completeFuture 是对 Future 的扩展实现,主要是为了弥补 Future 没有相应的回调机制的缺陷。

Callable、Runnable、Future、CompletableFuture 之间的关系:

  • Callable,有结果的同步行为,比如做蛋糕,产生蛋糕。
  • Runnable,无结果的同步行为,比如喝牛奶,仅仅就是喝。
  • Future,异步封装 Callable / Runnable,比如委托给师傅(其他线程)去做蛋糕,我去喝牛奶。但是蛋糕做好不会通知我。
  • CompletableFuture,封装Future,使其拥有回调功能,委托师傅做蛋糕,我去喝牛奶,并且师傅蛋糕做好了还会通知我。

1、Callable、Future、FutureTask

直接继承 Thread 或者实现 Runnable 接口都可以创建线程,但是这两种方法都有一个问题就是:没有返回值,也就是不能获取执行完的结果。因此 java1.5 就提供了 Callable 接口来实现这一场景,而 Future 和 FutureTask 就可以和 Callable 接口配合起来使用。

1.1 Callable 和 Runnable 的区别

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

@FunctionalInterface
public interface Callable<V> {
    V call() throws Exception;
}

Callable 的 call 方法可以有返回值,可以声明抛出异常。和 Callable 配合的有一个 Future 类,通过 Future 可以了解任务执行情况,或者取消任务的执行,还可获取任务执行的结果,这些功能都是 Runnable 做不到的。

new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("通过Runnable方式执行任务");
    }
}).start();

FutureTask task = new FutureTask(new Callable() {
    @Override
    public Object call() throws Exception {
        System.out.println("通过Callable方式执行任务");
        Thread.sleep(3000);
        return "返回任务结果";
    }
});
new Thread(task).start();

1.2、使用案例

在维护促销活动时需要查询商品信息(包括商品基本信息、商品价格、商品库存、商品图片、商品销售状态等)。

这些信息分布在不同的业务中心,由不同的系统提供服务。如果采用同步方式,假设一个接口需要50ms,那么一个商品查询下来就需要 200ms-300ms,这对于我们来说是不满意的。如果使用 Future 改造则需要的就是最长耗时服务的接口,也就是50ms左右。

在这里插入图片描述
配合线程池多线程执行

package com.example.canal.CompleteFuture;

import java.util.concurrent.*;

public class Yang {

    private static ExecutorService executorService = Executors.newFixedThreadPool(5);

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<String> ft1 = new FutureTask<>(new T1Task());
        FutureTask<String> ft2 = new FutureTask<>(new T2Task());
        FutureTask<String> ft3 = new FutureTask<>(new T3Task());
        FutureTask<String> ft4 = new FutureTask<>(new T4Task());
        FutureTask<String> ft5 = new FutureTask<>(new T5Task());

        executorService.submit(ft1);
        executorService.submit(ft2);
        executorService.submit(ft3);
        executorService.submit(ft4);
        executorService.submit(ft5);

        // 获取执行结果
        System.out.println(ft1.get());
        System.out.println(ft2.get());
        System.out.println(ft3.get());
        System.out.println(ft4.get());
        System.out.println(ft5.get());

        executorService.shutdown();

    }

    static class T1Task implements Callable<String> {
        @Override
        public String call() throws Exception {
            System.out.println("T1: 查询商品基本信息...");
            TimeUnit.MILLISECONDS.sleep(50);
            return "商品基本信息查询成功";
        }
    }

    static class T2Task implements Callable<String> {
        @Override
        public String call() throws Exception {
            System.out.println("T2: 查询商品价格...");
            TimeUnit.MILLISECONDS.sleep(50);
            return "商品价格查询成功";
        }
    }

    static class T3Task implements Callable<String> {
        @Override
        public String call() throws Exception {
            System.out.println("T3: 查询商品库存...");
            TimeUnit.MILLISECONDS.sleep(50);
            return "商品库存查询成功";
        }
    }

    static class T4Task implements Callable<String> {
        @Override
        public String call() throws Exception {
            System.out.println("T4: 查询商品图片...");
            TimeUnit.MILLISECONDS.sleep(50);
            return "商品图片查询成功";
        }
    }

    static class T5Task implements Callable<String> {
        @Override
        public String call() throws Exception {
            System.out.println("T5: 查询商品销售状态...");
            TimeUnit.MILLISECONDS.sleep(50);
            return "商品销售状态查询成功";
        }
    }
}

1.3、Future的局限性

从本质上说,Future 表示一个异步计算的结果。它提供了 isDone() 来检测计算是否已经完成,并且在计算结束后,可以通过 get() 方法来获取计算结果。在异步计算中,Future 确实是个非常优秀的接口。但是,它的本身也确实存在着许多限制:

  • 并发执行多任务:Future 只提供了 get()方法来获取结果,并且是阻塞的。所以,除了等待你别无他法。
  • 无法对多个任务进行链式调用:如果你希望在计算任务完成后执行特定动作,比如发邮件,但 Future 却没有提供这样的能力。
  • 无法组合多个任务:如果你运行了10个任务,并期望在它们全部执行结束后执行特定动作,那么在 Future 中这是无能为力的。
  • 没有异常处理:Future 接口中没有关于异常处理的方法。

所以,我们还需要CompletionService来帮助我们完成这些需求。

1.4、Future 注意事项

  • 当 for 循环批量获取 Future 的结果时容易 block,get 方法调用时应使用 timeout 限制。
  • Future 的生命周期不能后退。一旦完成了任务,它就永久停在了“已完成”的状态,不能从头再来。

2、CompletionService

2.1、CompletionService 原理

Callable + Future 可以实现多个task并行执行,但是如果遇到前面的 task 执行较慢时需要阻塞等待前面的 task 执行完后面 task 才能取得结果。而 CompletionService 的主要功能就是一边生成任务,一边获取任务的返回值。让两件事分开执行,任务之间不会互相阻塞,可以实现先执行完的先取结果,不再依赖任务顺序了。

2.2、使用案例

应用:向不同电商平台询价,并保存价格。

// 创建线程池 
ExecutorService executor = Executors.newFixedThreadPool(3); 
// 异步向电商S1询价 
Future<Integer> f1 = executor.submit(() -> getPriceByS1()); 
// 异步向电商S2询价 
Future<Integer> f2 = executor.submit(() -> getPriceByS2());             
// 获取电商S1报价并异步保存 
executor.execute(() -> save(f1.get()));        
// 获取电商S2报价并异步保存 
executor.execute(() -> save(f2.get()) 

如果获取电商S1报价的耗时很长,那么即便获取电商S2报价的耗时很短,也无法让保存S2报价的操作先执行,因为这个主线程都阻塞 在了 f1 的 get() 操作上。

使用 CompletionService 实现先获取的报价先保存到数据库

// 创建线程池
ExecutorService executor = Executors.newFixedThreadPool(10);
// 创建CompletionService
CompletionService<Integer> cs = new ExecutorCompletionService<>(executor);
// 异步向电商S1询价
cs.submit(() -> getPriceByS1());
// 异步向电商S2询价
cs.submit(() -> getPriceByS2());
//异步向电商S3询价
cs.submit(() -> getPriceByS3());
// 将询价结果异步保存到数据库
for (int i = 0; i < 3; i++) {
    Integer r = cs.take().get();
    executor.execute(() -> save(r));
}

2.3、应用场景总结

  • 当需要批量提交异步任务的时候建议你使用 CompletionService。CompletionService 将线程池 Executor 和阻塞队列 BlockingQueue 的功能融合在了一起,能够让批量异步任务的管理更简单。
  • CompletionService 能够让异步任务的执行结果有序化。先执行完的先进入阻塞队列,利用这个特性,你可以轻松实现后续处理的有序性,避免无谓的等待,同时还可以快速实现诸如 Forking Cluster 这样的需求。
  • 线程池隔离。CompletionService 支持自己创建线程池,这种隔离性能避免几个特别耗时的任务拖垮整个应用的风险。

3、CompletableFuture

CompletableFuture 是 Future 接口的扩展和增强。CompletableFuture 实现了 Future 接口,并在此基础上进行了丰富地扩展,完美地弥补了 Future 上述的种种问题。

更为重要的是,CompletableFuture 实现了对任务的编排能力。借助这项能力,我们可以轻松地组织不同任务的运行顺序、规则以及方式。从某种程度上说,这项能力是它的核心能力。而在以往,虽然通过 CountDownLatch 等工具类也可以实现任务的编排,但需要复杂的逻辑处理,不仅耗费精力且难以维护。

在这里插入图片描述

CompletionStage: 执行某一阶段,可向下执行后续阶段。异步执行,默认线程池是 ForkJoinPool 。

3.1、创建异步操作

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 作为它的线程池执行异步代码。如果指定线程池,则使用指定的线程池运行。
  • runAsync 方法以 Runnable函数式接口类型为参数,没有返回结果,supplyAsync 方法以 Supplier函数式接口类型为参数,返回结果类型为U,Supplier 接口的 get() 方法会有返回值的但是会阻塞线程。

使用默认的线程池就会出现一个问题,在主线程任务执行完以后,如果异步线程执行任务还没执行完就会直接把线程清除掉,因为默认线程池中的都是守护线程 forkjoinpool,当没有用户线程以后,会随着 jvm 一起清除,可以在主线程中阻塞几秒来解决,但是这样的编码显得格外的不优雅!

3.1.1、runAsync

package com.example.canal.CompleteFuture;

import java.util.concurrent.*;

public class Yang {

    private static CountDownLatch countDownLatch = new CountDownLatch(1);

    public static void main(String[] args) throws InterruptedException {
        // 执行无返回结果的异步任务
        Runnable runnable = () -> {
            System.out.println("[" + Thread.currentThread().getName() + "]" + "执行无返回结果的异步任务");
            countDownLatch.countDown();
        };

        CompletableFuture.runAsync(runnable);

        countDownLatch.await();
    }
}

// 结果
// [ForkJoinPool.commonPool-worker-25]执行无返回结果的异步任务

3.1.2、supplyAsync

package com.example.canal.CompleteFuture;

import java.util.concurrent.*;

public class Yang {
    
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        // 执行有返回结果的异步任务
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            System.out.println("[" + Thread.currentThread().getName() + "]" + "执行有返回值的异步任务");
            return "Hello World";
        });
        String s = future.get();
        System.out.println(s);
    }
}

// 结果
// [ForkJoinPool.commonPool-worker-25]执行有返回值的异步任务
// Hello World

3.2、获取结果

public T get() throws InterruptedException, ExecutionException

public T join()

join()和 get()方法都是用来获取 CompletableFuture 异步之后的返回值。

3.2.1、get & join

两者的主要区别在于,join()方法产生的是 RuntimeException,get()方法抛出的是受检异常(ExecutionException, InterruptedException )需要用户手动处理。

package com.example.canal.CompleteFuture;

import java.util.concurrent.*;

public class Yang {

    public static void main(String[] args) throws InterruptedException, ExecutionException {
        // 执行有返回结果的异步任务
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            System.out.println("[" + Thread.currentThread().getName() + "]" + "执行有返回值的异步任务");
            return "Hello World";
        });
        String s = future.get();
        System.out.println(s);
    }
}

// 结果
// [ForkJoinPool.commonPool-worker-25]执行有返回值的异步任务
// Hello World

3.3、结果处理

当 CompletableFuture 的计算结果完成或者抛出异常的时候,我们可以执行特定的 Action。主要是下面的方法:

public CompletableFuture<T> whenComplete(BiConsumer<? super T,? super Throwable> action)
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action)
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action, Executor executor)

public CompletableFuture<T> exceptionally(Function<Throwable, ? extends T> fn) 
  • Action 的类型是 BiConsumer<? super T,? super Throwable>,它可以处理正常的计算结果,或者异常情况。
  • 方法不以 Async 结尾,意味着 Action 使用相同的线程执行,而 Async 可能会使用其它的线程去执行(如果使用相同的线程池,也可能会被同一个线程选中执行)。
  • 这几个方法都会返回 CompletableFuture,当 Action 执行完毕后它的结果返回原始的 CompletableFuture 的计算结果或者返回异常。

3.3.1、whenComplete & exceptionally

package com.example.canal.CompleteFuture;

import java.util.Random;
import java.util.concurrent.*;
import java.util.function.BiConsumer;
import java.util.function.Function;

public class Yang {

    private static CountDownLatch countDownLatch = new CountDownLatch(1);

    public static void main(String[] args) throws InterruptedException, ExecutionException {

        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            countDownLatch.countDown();
            if (new Random().nextInt(10) % 2 == 0) {
                int i = 12 / 0;
            }
            System.out.println("执行结束!");
            return "test";
        }).whenComplete(new BiConsumer<String, Throwable>() {
            @Override
            public void accept(String s, Throwable throwable) {
                System.out.println(s + "执行完成!");
            }
        }).exceptionally(new Function<Throwable, String>() {
            @Override
            public String apply(Throwable throwable) {
                System.out.println("执行失败:" + throwable.getMessage());
                return "异常";
            }
        });
        countDownLatch.await();
    }
}

// 不出现异常结果
// 执行结束!
// test执行完成!

// 出现异常结果
// null执行完成!
// 执行失败:java.lang.ArithmeticException: / by zero

3.4、结果转换

所谓结果转换,就是将上一段任务的执行结果作为下一阶段任务的入参参与重新计算,产生新的结果。

public <U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn)
public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn)

public <U> CompletableFuture<U> thenCompose(Function<? super T, ? extends CompletionStage<U>> fn);
public <U> CompletableFuture<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn);
  • thenApply 转换的是泛型中的类型,返回的是同一个CompletableFuture。
  • thenCompose 将内部的 CompletableFuture 调用展开来并使用上一个 CompletableFutre 调用的结果在下一步的 CompletableFuture 调用中进行运算,是生成一个新的CompletableFuture。

3.4.1、thenApply

thenApply 接收一个函数作为参数,使用该函数处理上一个CompletableFuture 调用的结果,并返回一个具有处理结果的Future对象。

package com.example.canal.CompleteFuture;

import java.util.concurrent.*;

public class Yang {
    private static CountDownLatch countDownLatch = new CountDownLatch(1);

    public static void main(String[] args) throws InterruptedException, ExecutionException {
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello");
        CompletableFuture<String> result1 = future.thenApply(param -> param + " World!");

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

        countDownLatch.await();
    }
}
// 结果
// Hello World!

3.4.2、thenCompose

thenCompose 的参数为一个返回 CompletableFuture 实例的函数,该函数的参数是先前计算步骤的结果。

package com.example.canal.CompleteFuture;

import java.util.concurrent.*;

public class Yang {
    private static CountDownLatch countDownLatch = new CountDownLatch(1);

    public static void main(String[] args) throws InterruptedException, ExecutionException {
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello");

        CompletableFuture<String> result2 =
                future.thenCompose(param -> CompletableFuture.supplyAsync(() -> param + " World!"));

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

        countDownLatch.await();
    }
}

// 结果
// Hello World!

3.5、结果消费

与结果处理和结果转换系列函数返回一个新的 CompletableFuture 不同,结果消费系列函数只对结果执行Action,而不返回新的计算值。

根据对结果的处理方式,结果消费函数又分为:

  • thenAccept系列:对单个结果进行消费
  • thenAcceptBoth系列:对两个结果进行消费
  • thenRun系列:不关心结果,只对结果执行Action
public CompletionStage<Void> thenAccept(Consumer<? super T> action);
public CompletionStage<Void> thenAcceptAsync(Consumer<? super T> action);

public <U> CompletionStage<Void> thenAcceptBoth(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action);
public <U> CompletionStage<Void> thenAcceptBothAsync(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action);

public CompletionStage<Void> thenRun(Runnable action);
public CompletionStage<Void> thenRunAsync(Runnable action);

3.5.1、thenAccept

thenAccept 通过观察该系列函数的参数类型可知,它们是函数式接口 Consumer,这个接口只有输入,没有返回值。

package com.example.canal.CompleteFuture;

import java.util.Random;
import java.util.concurrent.*;

public class Yang {
    private static CountDownLatch countDownLatch = new CountDownLatch(1);

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture.supplyAsync(() -> {
            int number = new Random().nextInt(10);
            System.out.println("第一阶段:" + number);
            return number;
        }).thenApply(number -> {
            int result = number * 5;
            System.out.println("第二阶段:" + result);
            return result;
        });

        countDownLatch.await();
    }
}

3.5.2、thenAcceptBoth

thenAcceptBoth 函数的作用是,当两个 CompletionStage 都正常完成计算的时候,就会执行提供的action消费两个异步的结果。

package com.example.canal.CompleteFuture;

import java.util.Random;
import java.util.concurrent.*;

public class Yang {
    private static CountDownLatch countDownLatch = new CountDownLatch(1);

    public static void main(String[] args) throws InterruptedException {
        CompletableFuture<Integer> futrue1 = CompletableFuture.supplyAsync(() -> {
            int number1 = new Random().nextInt(3) + 1;
            System.out.println("第一阶段:" + number1);
            return number1;
        });

        CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {
            int number2 = new Random().nextInt(3) + 1;
            System.out.println("第二阶段:" + number2);
            return number2;
        });

        futrue1.thenAcceptBoth(future2, (number1, number2) -> System.out.println("最终结果:" + (number1 + number2)));

        countDownLatch.await();
    }
}

// 结果
// 第一阶段:1
// 第二阶段:3
// 最终结果:4

3.5.3、thenRun

thenRun 也是对线程任务结果的一种消费函数,与 thenAccept 不同的是,thenRun 会在上一阶段 CompletableFuture 计算完成的时候执行一个Runnable,Runnable并不使用该 CompletableFuture 计算的结果。

package com.example.canal.CompleteFuture;

import java.util.Random;
import java.util.concurrent.*;

public class Yang {
    private static CountDownLatch countDownLatch = new CountDownLatch(1);

    public static void main(String[] args) throws InterruptedException {
        CompletableFuture.supplyAsync(() -> {
            int number = new Random().nextInt(10);
            System.out.println("第一阶段:" + number);
            return number;
        }).thenRun(() -> System.out.println("thenRun()执行..."));

        countDownLatch.await();
    }
}

// 结果
// 第一阶段:5
// thenRun()执行...

3.6、结果组合

public <U,V> CompletionStage<V> thenCombine(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn);

public <U,V> CompletionStage<V> thenCombineAsync(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn);

3.6.1、thenCombine

thenCombine 方法,合并两个线程任务的结果,并进一步处理。

package com.example.canal.CompleteFuture;

import java.util.Random;
import java.util.concurrent.*;

public class Yang {
    private static final CountDownLatch countDownLatch = new CountDownLatch(1);

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
            int number1 = new Random().nextInt(10);
            System.out.println("第一阶段:" + number1);
            return number1;
        });

        CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {
            int number2 = new Random().nextInt(10);
            System.out.println("第二阶段:" + number2);
            return number2;
        });

        CompletableFuture<Integer> result = future1.thenCombine(future2, (number1, number2) -> number1 + number2);
        System.out.println("最终结果:" + result.get());

        countDownLatch.await();
    }
}

// 结果
// 第一阶段:1
// 第二阶段:5
// 最终结果:6

3.7、任务交互

public <U> CompletionStage<U> applyToEither(CompletionStage<? extends T> other,Function<? super T, U> fn);
public <U> CompletionStage<U> applyToEitherAsync(CompletionStage<? extends T> other,Function<? super T, U> fn);

public CompletionStage<Void> acceptEither(CompletionStage<? extends T> other,Consumer<? super T> action);
public CompletionStage<Void> acceptEitherAsync(CompletionStage<? extends T> other,Consumer<? super T> action);

public CompletionStage<Void> runAfterEither(CompletionStage<?> other,Runnable action);
public CompletionStage<Void> runAfterEitherAsync(CompletionStage<?> other,Runnable action);

public CompletionStage<Void> runAfterBoth(CompletionStage<?> other,Runnable action);
public CompletionStage<Void> runAfterBothAsync(CompletionStage<?> other,Runnable action);

public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs)

public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs)

所谓线程交互,是指将两个线程任务获取结果的速度相比较,按一定的规则进行下一步处理。

3.7.1、applyToEither

applyToEither 两个线程任务相比较,先获得执行结果的,就对该结果进行下一步的转化操作。

package com.example.canal.CompleteFuture;

import java.util.Random;
import java.util.concurrent.*;

public class Yang {
    private static final CountDownLatch countDownLatch = new CountDownLatch(1);

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
            int number = new Random().nextInt(10);
            System.out.println("第一阶段start:" + number);
            try {
                TimeUnit.SECONDS.sleep(number);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("第一阶段end:" + number);
            return number;
        });

        CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {
            int number = new Random().nextInt(10);
            System.out.println("第二阶段start:" + number);
            try {
                TimeUnit.SECONDS.sleep(number);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("第二阶段end:" + number);
            return number;
        });

        future1.applyToEither(future2, number -> {
            System.out.println("最快结果:" + number);
            return number * 2;
        });
        countDownLatch.await();
    }
}

// 结果
// 第一阶段start:3
// 第二阶段start:1
// 第二阶段end:1
// 最快结果:1
// 第一阶段end:3

3.7.2、acceptEither

acceptEither 两个线程任务相比较,先获得执行结果的,就对该结果进行下一步的消费操作。

演示

package com.example.canal.CompleteFuture;

import java.util.Random;
import java.util.concurrent.*;

public class Yang {
    private static final CountDownLatch countDownLatch = new CountDownLatch(1);

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
            int number = new Random().nextInt(10) + 1;
            try {
                TimeUnit.SECONDS.sleep(number);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("第一阶段:" + number);
            return number;
        });

        CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {
            int number = new Random().nextInt(10) + 1;
            try {
                TimeUnit.SECONDS.sleep(number);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("第二阶段:" + number);
            return number;
        });

        future1.acceptEither(future2, number -> System.out.println("最快结果:" + number));
        countDownLatch.await();
    }

}

// 结果
// 第二阶段:5
// 最快结果:5
// 第一阶段:10

3.7.3、runAfterEither

runAfterEither 两个线程任务相比较,有任何一个执行完成,就进行下一步操作,不关心运行结果。

演示

package com.example.canal.CompleteFuture;

import java.util.Random;
import java.util.concurrent.*;

public class Yang {
    private static final CountDownLatch countDownLatch = new CountDownLatch(1);

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
            int number = new Random().nextInt(5);
            try {
                TimeUnit.SECONDS.sleep(number);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("第一阶段:" + number);
            return number;
        });

        CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {
            int number = new Random().nextInt(5);
            try {
                TimeUnit.SECONDS.sleep(number);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("第二阶段:" + number);
            return number;
        });

        future1.runAfterEither(future2, () -> System.out.println("已经有一个任务完成了")).join();

        countDownLatch.await();
    }
}

// 结果
// 第一阶段:1
// 已经有一个任务完成了
// 第二阶段:2

3.7.4、runAfterBoth

runAfterBoth 两个线程任务相比较,两个全部执行完成,才进行下一步操作,不关心运行结果。
演示

package com.example.canal.CompleteFuture;

import java.util.Random;
import java.util.concurrent.*;

public class Yang {
    private static final CountDownLatch countDownLatch = new CountDownLatch(1);

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("第一阶段:1");
            return 1;
        });

        CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("第二阶段:2");
            return 2;
        });

        future1.runAfterBoth(future2, () -> System.out.println("上面两个任务都执行完成了。"));

        countDownLatch.await();
    }
}

// 结果
// 第一阶段:1
// 第二阶段:2
// 上面两个任务都执行完成了。

3.7.5、anyOf

anyOf 方法的参数是多个给定的 CompletableFuture,当其中的任何一个完成时,方法返回 CompletableFuture。

package com.example.canal.CompleteFuture;

import java.util.Random;
import java.util.concurrent.*;

public class Yang {
    private static final CountDownLatch countDownLatch = new CountDownLatch(1);

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        Random random = new Random();
        CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(random.nextInt(5));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "hello";
        });

        CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(random.nextInt(1));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "world";
        });
        CompletableFuture<Object> result = CompletableFuture.anyOf(future1, future2);
        System.out.println(result.get());

        countDownLatch.await();
    }
}

// 结果
// world

3.7.6、allOf

allOf方法用来实现多 CompletableFuture 的同时返回。

package com.example.canal.CompleteFuture;

import java.util.Random;
import java.util.concurrent.*;

public class Yang {
    private static final CountDownLatch countDownLatch = new CountDownLatch(1);

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("future1完成!");
            return "future1完成!";
        });

        CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
            System.out.println("future2完成!");
            return "future2完成!";
        });

        CompletableFuture<Void> combindFuture = CompletableFuture.allOf(future1, future2);
        try {
            combindFuture.get();
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }

        countDownLatch.await();
    }
}

// 结果
// future2完成!
// future1完成!

4、CompletableFuture 常用方法总结

分类方法说明返回值
异步执行一个线程runAsync默认 ForkJoinPool 线程池无返回值
异步执行一个线程supplyAsync默认 ForkJoinPool 线程池有返回值
分类方法说明返回值
两个线程依次执行thenApply获取前一个线程的结果,转换有返回值
两个线程依次执行thenAccept获取前一个线程的结果,消费无返回值
两个线程依次执行thenRun忽略前一个线程的结果,执行额外的逻辑无返回值
两个线程依次执行whenComplete获取前一个线程的结果或异常,消费不影响上一线程返回值
两个线程依次执行exceptionally线程异常执行,配合whenComplete 使用有返回值
两个线程依次执行handle相当于whenComplete + exceptionally有返回值
分类方法说明返回值
等待2个线程都执行完thenCombine2个线程都要有返回值,等待都结束,结果合并转换有返回值
等待2个线程执都行完thenAcceptBoth2个线程都要有返回值,等待都结束,结果合并消费无返回值
等待2个线程执都行完runAfterBoth2个线程无需要有返回值,等待都结束,执行其他逻辑无返回值
分类方法说明返回值
等待2个线程任一执行完applyToEither2个线程都要有返回值,等待任一结束,转换其结果有返回值
等待2个线程任一执行完acceptEither2个线程都要有返回值,等待任一结束,消费其结果无返回值
等待2个线程任一执行完runAfterEither2个线程无需有返回值,等待任一结束,执行其他逻辑无返回值
分类方法说明返回值
多个线程等待anyOf多个线程任一执行完返回有返回值
多个线程等待allOf多个线程全部执行完返回无返回值

5、Java8 CompleteFuture 简单使用

我们先看看 Java8 之前的 Future 的使用:

package com.example.canal.CompleteFuture;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;


public class Yang {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService cachePool = Executors.newCachedThreadPool();
        Future<String> future = cachePool.submit(() -> {
            Thread.sleep(3000);
            return "异步任务计算结果!";
        });

        // 提交完异步任务后,主线程可以继续干一些其他的事情
        doSomeThingElse();

        // 为了获取异步计算结果,我们可以通过 future.get 和 轮询机制来获取
        String result;

        // Get 方式会导致当前线程阻塞, 这显然违背了异步计算的初衷
        // result = future.get();

        // 轮询方式虽然不会导致当前线程阻塞, 但是会导致高额的 CPU 负载
        long start = System.currentTimeMillis();
        while (true) {
            if (future.isDone()) {
                break;
            }
        }
        System.out.println("轮询耗时:" + (System.currentTimeMillis() - start));

        result = future.get();
        System.out.println("获取到异步计算结果啦: " + result);

        cachePool.shutdown();
    }

    private static void doSomeThingElse() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("主线程正在做事.....");
    }
}

输出:

主线程正在做事.....
轮询耗时:1998
获取到异步计算结果啦: 异步任务计算结果!

从上面的 Demo 中我们可以看出,future 在执行异步任务时,,对于结果的获取显的不那么优雅,很多第三方库就针对 Future 提供了回调式的接口以用来获取异步计算结果,如Google的: ListenableFuture,而 Java8 所提供的 CompleteFuture 便是官方为了弥补这方面的不足而提供的 API。

下面简单介绍用法:

package com.example.canal.CompleteFuture;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Yang {

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        CompletableFuture<String> completableFutureOne = new CompletableFuture<>();

        ExecutorService cachePool = Executors.newCachedThreadPool();
        cachePool.execute(() -> {
            try {
                Thread.sleep(3000);
                completableFutureOne.complete("异步任务执行结果");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        // WhenComplete 方法返回的 CompletableFuture 仍然是原来的 CompletableFuture 计算结果
        CompletableFuture<String> completableFutureTwo = completableFutureOne.whenComplete((s, throwable) -> {
            System.out.println("当异步任务执行完毕时打印异步任务的执行结果: " + s);
        });

        // ThenApply 方法返回的是一个新的 completeFuture
        CompletableFuture<Integer> completableFutureThree = completableFutureTwo.thenApply(s -> {
            System.out.println("当异步任务执行结束时, 根据上一次的异步任务结果, 继续开始一个新的异步任务!");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return s.length();
        });

        System.out.println("阻塞方式获取执行结果:" + completableFutureThree.get());

        cachePool.shutdown();
    }
}

从上面的 Demo 中我们主要需要注意 thenApply 和 whenComplete 这两个方法,这两个方法便是 CompleteFuture 中最具有意义的方法,,他们都会在 completeFuture 调用,complete 方法传入异步计算结果时回调,从而获取到异步任务的结果。

相比之下 future 的阻塞和轮询方式获取异步任务的计算结果,CompleteFuture 获取结果的方式就显的优雅的多。

6、实现最优的 烧水泡茶 程序

著名数学家华罗庚先生在《统筹方法》这篇文章里介绍了一个烧水泡茶的例子,文中提到最优的工序应该是下面这样:

在这里插入图片描述
对于烧水泡茶这个程序,一种最优的分工方案:用两个线程 T1 和 T2 来完成烧水泡茶程序,T1 负责洗水壶,烧开水,泡茶,T2 负责洗茶壶,洗茶杯,拿茶叶。

6.1、基于Future实现

package com.example.canal.CompleteFuture;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;

public class Yang {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 创建任务T2的FutureTask
        FutureTask<String> ft2 = new FutureTask<>(new T2Task());
        // 创建任务T1的FutureTask
        FutureTask<String> ft1 = new FutureTask<>(new T1Task(ft2));

        // 线程T1执行任务ft1
        Thread T1 = new Thread(ft1);
        T1.start();
        // 线程T2执行任务ft2
        Thread T2 = new Thread(ft2);
        T2.start();
        // 等待线程T1执行结果
        System.out.println(ft1.get());

    }
}

// T1 需要执行的任务:洗水壶、烧开水、泡茶
class T1Task implements Callable<String> {
    FutureTask<String> ft2;

    // T1任务需要T2任务的FutureTask
    T1Task(FutureTask<String> ft2) {
        this.ft2 = ft2;
    }

    @Override
    public String call() throws Exception {
        System.out.println("T1:洗水壶...");
        TimeUnit.SECONDS.sleep(1);

        System.out.println("T1:烧开水...");
        TimeUnit.SECONDS.sleep(15);

        // 获取T2线程的茶叶
        String tf = ft2.get();
        System.out.println("T1:拿到茶叶:" + tf);

        System.out.println("T1:泡茶...");
        return "上茶:" + tf;
    }
}

// T2 需要执行的任务:洗茶壶、洗茶杯、拿茶叶
class T2Task implements Callable<String> {
    @Override
    public String call() throws Exception {
        System.out.println("T2:洗茶壶...");
        TimeUnit.SECONDS.sleep(1);

        System.out.println("T2:洗茶杯...");
        TimeUnit.SECONDS.sleep(2);

        System.out.println("T2:拿茶叶...");
        TimeUnit.SECONDS.sleep(1);
        return "绿茶";
    }
}

// T1:洗水壶...
// T2:洗茶壶...
// T1:烧开水...
// T2:洗茶杯...
// T2:拿茶叶...
// T1:拿到茶叶:绿茶
// T1:泡茶...
// 上茶:绿茶

6.2、基于CompletableFuture实现

package com.example.canal.CompleteFuture;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

public class Yang {

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        // 任务1:洗水壶、烧开水
        CompletableFuture<Void> f1 = CompletableFuture.runAsync(() -> {
            System.out.println("T1:洗水壶...");
            sleep(1, TimeUnit.SECONDS);

            System.out.println("T1:烧开水...");
            sleep(15, TimeUnit.SECONDS);
        });

        // 任务2:洗茶壶、洗茶杯、拿茶叶
        CompletableFuture<String> f2 = CompletableFuture.supplyAsync(() -> {
            System.out.println("T2:洗茶壶...");
            sleep(1, TimeUnit.SECONDS);

            System.out.println("T2:洗茶杯...");
            sleep(2, TimeUnit.SECONDS);

            System.out.println("T2:拿茶叶...");
            sleep(1, TimeUnit.SECONDS);
            return "龙井";
        });

        // 任务3:任务1和任务2完成后执行:泡茶
        CompletableFuture<String> f3 = f1.thenCombine(f2, (__, tf) -> {
            System.out.println("T1:拿到茶叶:" + tf);
            System.out.println("T1:泡茶...");
            return "上茶:" + tf;
        });
        // 等待任务3执行结果
        System.out.println(f3.get());
    }

    static void sleep(int t, TimeUnit u) {
        try {
            u.sleep(t);
        } catch (InterruptedException e) {
        }
    }
}

// T1:洗水壶...
// T2:洗茶壶...
// T2:洗茶杯...
// T1:烧开水...
// T2:拿茶叶...
// T1:拿到茶叶:龙井
// T1:泡茶...
// 上茶:龙井

7、demo

​ 场景1:需要查询一个订单信息,首先需要查询商品信息,然后查询支付信息,最后汇总成一个对象返回。

package com.example.canal.CompleteFuture;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

public class Yang {

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "商品信息")
            .thenCombineAsync(CompletableFuture.supplyAsync(() -> "支付信息"), (a, b) -> {
                // 组装信息
                return a + b;
            });
        System.out.println(future.get());
    }
}

​ 场景2:用户注册,首先需要校验用户信息,然后生成账号信息,最后保存到数据库。这三个操作互相依赖。

package com.example.canal.CompleteFuture;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadLocalRandom;

public class Yang {

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        CompletableFuture<String> future = CompletableFuture.runAsync(() -> {
            if (ThreadLocalRandom.current().nextBoolean()) {
                return;
            }
            // throw new RuntimeException("该手机号码已经注册");
        }).thenCompose(ret -> CompletableFuture.supplyAsync(() -> {
            if (ThreadLocalRandom.current().nextBoolean()) {
                // 生成账号信息
                return "账号信息: 139926";
            }
            throw new RuntimeException("账号信息生成失败。。");
        })).thenApplyAsync(ret -> {
            // 保存账号信息
            System.out.println("保存账号信息->{}" + ret);
            return "注册成功";
        }).exceptionally(e -> "注册失败" + e.getMessage());

        System.out.println("最终返回结果:" + future.get());
    }
}

http://lihuaxi.xjx100.cn/news/839037.html

相关文章

AWS攻略——VPC初识

大纲在网络里启动一台可以ssh上去的机器查看区域、VPC和子网创建EC2连接Web端连接客户端连接知识点参考资料VPC是在AWS架构服务的基础&#xff0c;有点类似于我们在机房里拉网线和设置路由器等。等这些设施完备后&#xff0c;我们才能考虑给机器部署服务。而很多初识AWS的同学&…

redis高级特性学习(慢查询、Pipeline、事务、Lua)

Redis高级特性和应用(慢查询、Pipeline、事务、Lua) Redis的慢查询 许多存储系统&#xff08;例如 MySQL)提供慢查询日志帮助开发和运维人员定位系统存在的慢操作。所谓慢查询日志就是系统在命令执行前后计算每条命令的执行时间&#xff0c;当超过预设阀值,就将这条命令的相关…

maven本地私服取jar包后再进行反编译获取源代码

1.先去maven安装目录寻找setting.xml D:\maven\apache-maven-3.1.1\conf 利用记事本打开。 寻找标签 nexus central nexus mirror. http://XXX.XXX.XX.XX:8080/content/groups/public url就是本地私库的链接&#xff0c;复制链接在浏览器打开 2.找到你所需要找的jar包&…

2月7日 读书笔记

是鼠标移动的思路&#xff1a;调整坐标重新显示 当CPU进行模式转换和PIC进行初始化的时候&#xff0c;都不允许有中断发生&#xff0c;因此要将中断屏蔽掉。 汇编指令NOP只是让CPU休息一个时钟长的时间。 这条汇编语句&#xff1a; MOV AL,0xdf是要让A20GATE信号线变成ON…

小知识点:Airflow 安装部署

安装流程一、Python 安装二、Airflow 安装三、Airflow 配置四、存在的坑部分安装方法参考&#xff1a; Linux 虚拟机&#xff1a;大数据集群基础环境搭建&#xff08;Hadoop、Spark、Flink、Hive、Zookeeper、Kafka、Nginx&#xff09; 一、Python 安装 当前安装版本为 Pytho…

在Vivado下如何判断芯片是多die芯片-关于SSI的多方面解答

1. 什么是SSI芯片?SSI是Stacked Silicon Interconnect的缩写。SSI芯片其实就是我们通常所说的多die芯片。其基本结构如下图所示。可以看到SSI芯片的基本单元是SLR(Super Logic Region),也就是我们所说的die。SLR之间通过Interposer“粘合”在一起。每个SLR可看做一片小规模…

拒绝盲目自学!完整Java学习路线图,全面对标阿里P5-P8职级实现弯道超车

前言 对于大部分的程序员来说&#xff0c;清晰地规划自己的职业发展并不是一件容易的事情。作为一个曾经底子比较差&#xff0c;从底层摸爬滚打多年走过来的程序员&#xff0c;在这里分享一下对我帮助很大的一份宝典&#xff0c;希望同行们能快速掌握这些技术&#xff0c;直接…

1月榜单丨飞瓜数据B站UP主排行榜(哔哩哔哩平台)发布!

飞瓜轻数发布2023年1月飞瓜数据UP主排行榜&#xff08;B站平台&#xff09;&#xff0c;通过充电数、涨粉数、成长指数三个维度来体现UP主账号成长的情况&#xff0c;为用户提供B站号综合价值的数据参考&#xff0c;根据UP主成长情况用户能够快速找到运营能力强的B站UP主。飞瓜…