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("喂猫完成!");
}
}
解答思路:
- 做饭和洗衣服可以同时进行,因为它们没有依赖关系。两项同时进行需要 40 分钟(因为做饭更慢)。
- 做饭结束后,才能洗碗,洗碗需要 10 分钟。
- 洗衣服结束后,才能拖地,拖地需要 20 分钟。
- 喂猫可以随时进行,用 5 分钟完成,不影响其他任务。
总时间计算:
- 先做饭和洗衣服:40 分钟(同时进行)
- 做饭结束后洗碗:10 分钟
- 洗衣服结束后拖地:20 分钟
- 喂猫:5 分钟(可以在40分钟内的任意时刻完成,不增加时间)
所以,小明最少需要的时间为:40 + 10 = 50 分钟。