CompletableFuture异步任务编排

Callable

线程创建的几种方式,继承Thread类,实现Callable接口或Runnable接口。只有Callable接口支持返回值,可以拿到线程的状态,这也是线程异步任务编排的基础。

public class CallableDemo implements Callable {

    @Override
    public String call() throws Exception {
        System.out.println(Thread.currentThread().getName() + "线程执行执行");
        return Thread.currentThread().getName() + "over";
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CallableDemo callableDemo = new CallableDemo();
        FutureTask<CallableDemo> callableDemoFutureTask = new FutureTask<CallableDemo>(callableDemo);
        new Thread(callableDemoFutureTask).start();
        System.out.println(callableDemoFutureTask.get());
    }

}

虽然可以拿到返回值,但是要使用这个去操作线程的启停。还是很麻烦的

CompletableFuture

CompletableFuture 是 Java 8 引入的,用于支持异步编程的类。它允许你编排异步任务,并处理结果或异常。

CompletableFuture实现了Future接口,并在此基础上进行了丰富的扩展,完美弥补了Future的局限性,同时CompletableFuture实现了对任务编排的能力

构造方法

runAsyncm没有返回值 supplyAsync有返回值, executor用来指定线程池,不指定使用默认的线程池。

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)
获取结果

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

异常处理
  • get():在获取结果时,如果任务以异常结束,get() 会抛出 ExecutionException,需要额外处理。
  • join():在获取结果时,如果任务以异常结束,join() 会抛出 CompletionException,更简单地封装了异常。
阻塞行为
  • 两者都将阻塞调用线程,直到结果可用,但 get() 可以指定超时时间,而 join() 不支持超时
结果处理

方法不以Async结尾,意味着Action使用相同的线程执行,而Async可能会使用其它的线程去执行(如果使用相同的线程池,也可能会被同一个线程选中执行)。

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)
异常结果处理

exceptionally:返回一个新的CompletableFuture,当前面的CompletableFuture完成时,它也完成,当它异常完成时,给定函数的异常触发这个CompletableFuture的完成

场景

想起了小时候做的数学题,网上找了一个。尝试用程序的方式去解决这个问题。(时间会缩短到秒)

题目: 小明今天有很多家务需要做,任务如下:

  • 做饭需要 40 分钟
  • 洗衣服需要 30 分钟
  • 洗碗需要 10 分钟,但必须在做饭完成后才能进行
  • 拖地需要 20 分钟,但必须在洗衣服完成后才能进行
  • 喂猫需要 5 分钟,可以随时做

请问小明最少需要多少时间才能完成所有任务?

**答:**🤩

模拟场景
public class HouseholdTasks {

    // 做饭方法,阻塞模拟
    public void cook() throws InterruptedException {
        System.out.println("开始做饭...");
        // 模拟做饭时间较长,阻塞一会儿,比如4000毫秒
        Thread.sleep(4000);
        System.out.println("做饭完成!");
    }

    // 洗衣服方法,阻塞模拟
    public void washClothes() throws InterruptedException {
        System.out.println("开始洗衣服...");
        // 模拟洗衣服时间较长,阻塞一会儿,比如3000毫秒
        Thread.sleep(3000);
        System.out.println("洗衣服完成!");
    }

    // 洗碗方法,依赖于做饭完成
    public void washDishes() throws InterruptedException {
        System.out.println("开始洗碗...");
        // 洗碗依赖于做饭完成,阻塞模拟
        Thread.sleep(1000);
        System.out.println("洗碗完成!");
    }

    // 拖地方法,依赖于洗衣服完成
    public void mopFloor() throws InterruptedException {
        System.out.println("开始拖地...");
        // 拖地依赖于洗衣服完成,阻塞模拟
        Thread.sleep(2000);
        System.out.println("拖地完成!");
    }

    // 喂猫方法,可以随时执行
    public void feedCat() throws InterruptedException {
        System.out.println("开始喂猫...");
        // 喂猫可以随时进行,阻塞模拟
        Thread.sleep(500);
        System.out.println("喂猫完成!");
    }
}

解答思路:

  1. 做饭和洗衣服可以同时进行,因为它们没有依赖关系。两项同时进行需要 40 分钟(因为做饭更慢)。
  2. 做饭结束后,才能洗碗,洗碗需要 10 分钟。
  3. 洗衣服结束后,才能拖地,拖地需要 20 分钟。
  4. 喂猫可以随时进行,用 5 分钟完成,不影响其他任务。

总时间计算:

  • 先做饭和洗衣服:40 分钟(同时进行)
  • 做饭结束后洗碗:10 分钟
  • 洗衣服结束后拖地:20 分钟
  • 喂猫:5 分钟(可以在40分钟内的任意时刻完成,不增加时间)

所以,小明最少需要的时间为:40 + 10 = 50 分钟。