文章目录
前言
最近有一个接口需求,需要从多个服务获取数据返回给前端,如果串行一个一个获取的话,假如每个服务1s,5个服务就要5s,服务越多时间越久。
这时候,我们可以将串行获取改为并行获取,大大降低了耗时。
并行获取虽然提高了效率,但也有一个问题,如果某个服务异常阻塞,这个接口会一直等到这个服务超时返回(根据设置的超时时间),这时候就需要一个功能,那就是超时获取。
超时的并行获取,也是并行去获取数据,但我们规定了一个时间,比如1s,那么获取数据的操作就会在1s返回,没有在规定时间内返回的数据将会被丢弃。这样虽然数据会不全,但是总比一直阻塞没有结果好。
实现超时获取的两种方法如下。
Callable+Future实现
public class DataService {
private static final ExecutorService executorService = Executors.newFixedThreadPool(3);
public static void main(String[] args) {
// 创建多个获取数据的任务
List<Callable<String>> tasks = new ArrayList<>();
tasks.add(new Task("1", 1));
tasks.add(new Task("2", 1));
tasks.add(new Task("3", 3));
// 获取数据
String result = fetchData(tasks);
// 打印结果
System.out.println("Result: " + result);
// 关闭线程池
executorService.shutdown();
}
public static String fetchData(List<Callable<String>> tasks) {
try {
// 调用invokeAll方法,等待1秒内返回结果
List<Future<String>> futures = executorService.invokeAll(tasks, 1, TimeUnit.SECONDS);
// 获取所有任务的执行结果
List<String> results = new ArrayList<>();
for (Future<String> future : futures) {
if (future.isDone() && !future.isCancelled()) {
results.add(future.get());
}
}
return String.join(",", results);
} catch (InterruptedException e) {
// 超时异常,可以记录日志等
System.out.println("Timeout: " + e.getMessage());
} catch (ExecutionException e) {
// 其他异常处理
e.printStackTrace();
}
// 如果超时或发生异常,返回一个默认值或者进行其他处理
return "Default Result";
}
static class Task implements Callable<String> {
String serviceName;
long timeout;
Task(String serviceName, long timeout) {
this.serviceName = serviceName;
this.timeout = timeout;
}
@Override
public String call() throws Exception {
// 模拟从服务获取数据的逻辑
try {
Thread.sleep(TimeUnit.SECONDS.toMillis(timeout));
return "Data from Service " + serviceName;
} catch (InterruptedException e) {
e.printStackTrace();
return null;
}
}
}
}
// 输出
Result: Data from Service 1,Data from Service 2
在上述代码中,我们使用Callable
创建获取数据的任务,然后使用 invokeAll
提交所有任务,并设置了1秒的超时时间,等待任务的完成,如果任务都在1s内完成,会立即返回。对于每个任务,检查它是否已完成,如果已完成则获取结果,如果未完成或异常则记录日志。
CountDownLatch实现
public class DataService {
private static final ExecutorService executorService = Executors.newFixedThreadPool(3);
public static void main(String[] args) {
CountDownLatch countDownLatch = new CountDownLatch(3);
List<String> result = new CopyOnWriteArrayList<>();
executorService.submit(new Task("1", 500, result, countDownLatch));
executorService.submit(new Task("2", 500, result, countDownLatch));
executorService.submit(new Task("3", 1200, result, countDownLatch));
try {
// 等待所有任务完成,最长等待时间为1秒
countDownLatch.await(1, TimeUnit.SECONDS);
} catch (InterruptedException e) {
// 处理中断异常
throw new RuntimeException("Task execution interrupted", e);
}
// 打印结果
System.out.println("Results: " + result);
// 关闭线程池
executorService.shutdown();
}
static class Task implements Runnable {
String serviceName;
long timeout;
List<String> result;
CountDownLatch countDownLatch;
public Task(String serviceName, long timeout, List<String> result, CountDownLatch countDownLatch) {
this.serviceName = serviceName;
this.timeout = timeout;
this.result = result;
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
// 模拟从服务获取数据的逻辑
try {
Thread.sleep(TimeUnit.MILLISECONDS.toMillis(timeout));
result.add("Data from Service " + serviceName);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
countDownLatch.countDown();
}
}
}
}
// 输出
Results: [Data from Service 1, Data from Service 2]
在上述代码中,我们使用CountDownLatch
实现超时获取功能,使用线程池提交任务,任务完成时会调用countDown
方法,将计数器减1,countDownLatch.await
会等待1s,如果三个任务很快执行完成,都执行了countDown
方法,那么await
方法会立即返回。
新需求
上面我们已经完成了超时获取的功能,该接口会在1s内返回数据,这样可以提升用户的体验,超过1s的服务接口我们可以进行优化。
这时候又来了一个新的需求,并行获取服务1、2、3的数据,然后根据服务1返回的数据去获取服务4的数据,再将四个服务的数据全部返回。
Future实现
public class DataService {
private static final ExecutorService executorService = Executors.newFixedThreadPool(3);
public static void main(String[] args) {
// 使用Future获取服务1、服务2、服务3的数据
Future<String> result1 = executorService.submit(new Task("Service1"));
Future<String> result2 = executorService.submit(new Task("Service2"));
Future<String> result3 = executorService.submit(new Task("Service3"));
Future<String> result4 = executorService.submit(new Task4(result1));
try {
// 获取服务数据
String data1 = result1.get();
String data2 = result2.get();
String data3 = result3.get();
String data4 = result4.get();
System.out.println(data1);
System.out.println(data2);
System.out.println(data3);
System.out.println(data4);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
} finally {
executorService.shutdown();
}
}
static class Task implements Callable<String> {
private final String serviceName;
Task(String serviceName) {
this.serviceName = serviceName;
}
@Override
public String call() throws Exception {
return "Data from " + serviceName;
}
}
static class Task4 implements Callable<String> {
private final Future<String> future;
Task4(Future<String> future) {
this.future = future;
}
@Override
public String call() throws Exception {
while (!future.isDone()) {
try {
Thread.sleep(100); // 等待一段时间
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return "Data from service4 for " + future.get();
}
}
}
上述代码使用了 Future
来处理异步任务的结果,但存在一些不足之处:
- 使用
get
方法会阻塞当前线程,直到服务的数据返回,这样可能导致整体程序的性能下降。 - 循环调用
isDone
进行检查,会带来一定的轮询开销,造成CPU空转,导致不必要的上下文切换。 - 不支持异步任务的编排组合,无法方便地进行多个异步任务之间的链式操作,在处理多个异步任务时可能需要使用较为繁琐的手动管理方式。
- 不支持回调机制,无法轻松地在任务完成时触发后续的操作。
CompletableFuture介绍
为了弥补Future
的不足,Java 8 引入了 CompletableFuture
类,它是 Future
的扩展,并提供了更强大的异步编程工具。CompletableFuture
具有更灵活的非阻塞操作、更丰富的异常处理、支持链式操作、更好的组合多个任务等特性,使得异步编程更加方便和强大。
CompletableFuture
同时实现了Future
和CompletionStage
,Future
的主要接口在《一次搞定Thread、Runable、Callable、Future、FutureTask》介绍过了,我们主要看CompletionStage
。
可以看到CompletionStage
有很多方法,并且支持函数式编程,其被设计为用于组合和处理异步操作,为开发者提供了更灵活和强大的异步编程工具。
函数式接口
在介绍CompletableFuture
的方法之前,我们先来复习一下函数式接口,因为其中使用了大量的函数式接口。
函数式接口只包含了一个抽象方法,该接口被@FunctionalInterface
修饰,函数式接口为我们提供了函数式编程的支持,让我们能够更轻松地写出简洁而强大的代码。下面介绍五个常用的函数式接口。
Runnable
,run方法,不能接收参数,无返回值。
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
Function
,apply方法,接收一个参数,有返回值。
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
Consumer
,accept方法,接收一个参数,无返回值。
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
Supplier
,get方法,不接收参数,有返回值。
@FunctionalInterface
public interface Supplier<T> {
T get();
}
BiConsumer
,accept方法,接收两个参数,无返回值。
@FunctionalInterface
public interface BiConsumer<T, U> {
void accept(T t, U u);
}
常用方法
创建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);
runAsync
和supplyAsync
的区别主要在于接收的函数式接口不同,Runnable
接口没有返回值,Supplier
接口有返回值。executor
参数为用于执行任务的线程池,如果不传,使用默认的ForkJoinPool
线程池。
推荐使用自定义线程池
为什么推荐使用自定义线程池?我们来看一下源码
public static CompletableFuture<Void> runAsync(Runnable runnable) {
return asyncRunStage(asyncPool, runnable);
}
// 判断计算机处理器的核心数是否大于1
private static final boolean useCommonPool = (ForkJoinPool.getCommonPoolParallelism() > 1);
// 核心数大于1则使用ForkJoinPool线程池,否则每次都新建一个线程
private static final Executor asyncPool = useCommonPool ?
ForkJoinPool.commonPool() : new ThreadPerTaskExecutor();
// 创建一个线程执行
static final class ThreadPerTaskExecutor implements Executor {
public void execute(Runnable r) { new Thread(r).start(); }
}
如果你的电脑是2核,
ForkJoinPool.getCommonPoolParallelism()
获取的值有可能是1
,这种情况下,CompletableFuture
将会为每个任务创建一个新的线程。每个异步任务都会独占一个线程,会导致线程的频繁创建和销毁。另外如果使用默认的ForkJoinPool,会与其他使用ForkJoinPool的功能共享,导致线程争用,影响性能。
使用自定义线程池,可以更好地控制线程的生命周期和优化执行性能。
获取结果
public T get() throws InterruptedException, ExecutionException;
public T get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
public T join();
// 计算完成返回结果,没有完成返回指定值
public T getNow(T valueIfAbsent);
get()
方法声明了 InterruptedException
和 ExecutionException
异常,因此需要显式地处理异常。在等待任务完成的过程中,如果线程被中断,会抛出中断异常InterruptedException
。
get(long timeout, TimeUnit unit)
方法与get
不同在于指定了等待时间,超时未返回抛出TimeoutException异常。
join()
方法没有声明异常,执行过程中发生的异常被包装在 CompletionException
中,不需要显式地处理异常。
在不需要特别处理异常的情况下使用 join
方法。有特殊的异常处理需求,或者需要处理中断,使用 get(long timeout, TimeUnit unit)
方法,指定时间内返回。
异步回调方法
thenRun
public CompletableFuture<Void> thenRun(Runnable action);
public CompletableFuture<Void> thenRunAsync(Runnable action);
public CompletableFuture<Void> thenRunAsync(Runnable action, Executor executor);
thenRun
方法接收一个 Runnable
参数,该 Runnable
会在前一个异步任务正常完成后执行。不处理前一个阶段的结果,也不返回任何结果。
CompletableFuture<Void> future = CompletableFuture
.runAsync(() -> System.out.print("Hello "))
.thenRun(() -> System.out.println("CompletableFuture"));
future.join();
// 输出
Hello CompletableFuture
thenRun和thenRunAsync区别
- runAsync没有传入自定义线程池,默认都使用ForkJoinPool
public static void main(String[] args) {
CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> {
System.out.println("runAsync future1 " + Thread.currentThread().getName());
}).thenRun(() -> {
System.out.println("thenRun future1 " + Thread.currentThread().getName());
});
CompletableFuture<Void> future2 = CompletableFuture.runAsync(() -> {
System.out.println("runAsync future2 " + Thread.currentThread().getName());
}).thenRunAsync(() -> {
System.out.println("thenRunAsync future2 " + Thread.currentThread().getName());
});
CompletableFuture.allOf(future1, future2).join();
}
// 输出
runAsync future1 ForkJoinPool.commonPool-worker-1
thenRun future1 ForkJoinPool.commonPool-worker-1
runAsync future2 ForkJoinPool.commonPool-worker-1
thenRunAsync future2 ForkJoinPool.commonPool-worker-1
- runAsync传入自定义线程池,thenRun使用同一个线程池,thenRunAsync使用默认ForkJoinPool
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(3);
// runAsync传入自定义线程池
CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> {
System.out.println("runAsync future1 " + Thread.currentThread().getName());
}, executorService).thenRun(() -> {
System.out.println("thenRun future1 " + Thread.currentThread().getName());
});
// runAsync传入自定义线程池,thenRunAsync没传入自定义线程池
CompletableFuture<Void> future2 = CompletableFuture.runAsync(() -> {
System.out.println("runAsync future2 " + Thread.currentThread().getName());
}, executorService).thenRunAsync(() -> {
System.out.println("thenRunAsync future2 " + Thread.currentThread().getName());
});
// runAsync传入自定义线程池,thenRunAsync传入自定义线程池
CompletableFuture<Void> future3 = CompletableFuture.runAsync(() -> {
System.out.println("runAsync future3 " + Thread.currentThread().getName());
}, executorService).thenRunAsync(() -> {
System.out.println("thenRunAsync future3 " + Thread.currentThread().getName());
}, executorService);
CompletableFuture.allOf(future1, future2, future3).join();
executorService.shutdown();
}
// 输出
runAsync future1 pool-1-thread-1
thenRun future1 pool-1-thread-1
runAsync future2 pool-1-thread-2
thenRunAsync future2 ForkJoinPool.commonPool-worker-1
runAsync future3 pool-1-thread-3
thenRunAsync future3 pool-1-thread-1
- 可能处理太快,系统优化切换原则,直接使用主线程处理
public static void main(String[] args) {
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
System.out.println("runAsync future1 " + Thread.currentThread().getName());
}).thenRun(() -> {
System.out.println("thenRun future1 " + Thread.currentThread().getName());
});
future.join();
}
// 输出
runAsync future1 ForkJoinPool.commonPool-worker-1
thenRun future1 main
其他方法的同步方法和Async方法的区别,与thenRun一致。
thenApply
public <U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn);
thenApply
方法接收Function参数,用于处理上一个任务的正常结果,并返回一个新的结果。
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello")
.thenApply(result -> result + ", CompletableFuture!");
System.out.println(future.join()); // 输出:Hello, CompletableFuture!
thenAccept
public CompletableFuture<Void> thenAccept(Consumer<? super T> action);
thenAccept
方法接收Consumer参数,用于处理上一个任务的正常结果,但不返回新的结果。
CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> "Hello")
.thenAccept(result -> System.out.println(result + ", CompletableFuture!"));
future.join(); // 输出:Hello, CompletableFuture!
thenCombine
public <U,V> CompletableFuture<V> thenCombine(CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn);
thenCombine
用于组合两个 CompletableFuture
的结果,并返回一个新的结果。第一个参数为另一个CompletableFuture
,第二个参数为BiFunction
函数接口,接收两个参数,返回新的结果。
thenCombine
会在两个任务都执行完成后,把两个任务的结果合并。两个任务是并行执行的,它们之间并没有先后依赖顺序。
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello")
.thenCombine(CompletableFuture.supplyAsync(() -> "CompletableFuture"), (s1, s2) -> s1 + " " + s2);
System.out.println(combinedFuture.join()); // 输出:Hello CompletableFuture
whenComplete
上面四种方法,都只能处理正常的结果,即只有正常完成才会执行后续的回调方法。而whenComplete
无论正常完成还是抛出异常,都会执行回调方法,允许你处理任务的结果或异常,进行相应的操作。
public CompletableFuture<T> whenComplete(BiConsumer<? super T, ? super Throwable> action);
whenComplete
接收BiConsumer函数式接口,该接口接收两个参数,一个是正常完成的结果,另一个是任务抛出的异常,但不返回新的结果。
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
if (true) {
throw new RuntimeException("CompletableFuture error");
}
return "hello";
}).whenComplete((res, ex) -> {
if (ex != null) {
System.out.println(ex.getMessage());
} else {
System.out.println(res + " CompletableFuture!");
}
});
future.join();
// 输出:java.lang.RuntimeException: CompletableFuture error
抛出异常
异常处理
exceptionally
public CompletableFuture<T> exceptionally(Function<Throwable, ? extends T> fn);
exceptionally
用于处理异常情况,该回调方法会在 CompletableFuture
发生异常时触发执行。可以捕获异常并提供默认值或处理异常情况。
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> 10 / 0)
.exceptionally(ex -> {
System.out.println("Exception: " + ex.getMessage());
return 0;
});
System.out.println(future.join());
// 输出
Exception: java.lang.ArithmeticException: / by zero
0
handle
public <U> CompletableFuture<U> handle(BiFunction<? super T, Throwable, ? extends U> fn);
handle
方法会在 CompletableFuture
正常完成或发生异常时触发执行,可以处理正常结果和异常情况。参数是一个 BiFunction
,该函数接收计算的结果和异常信息作为参数,返回一个新的值。
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> 10 / 2)
.handle((result, ex) -> {
if (ex != null) {
System.out.println("Exception: " + ex.getMessage());
return 0;
} else {
return result * 2;
}
});
System.out.println(future.join()); // 输出:10
对计算速度进行选用
假如我们想要实现 任务1 和 任务2 中的任意一个执行完后就执行 任务3 的话,可以使用applyToEither
和acceptEither()
。
public <U> CompletableFuture<U> applyToEither(
CompletionStage<? extends T> other, Function<? super T, U> fn);
public CompletableFuture<Void> acceptEither(
CompletionStage<? extends T> other, Consumer<? super T> action);
上面两个方法第一个参数都是另一个异步任务CompletableFuture
,区别在于第二个参数。
applyToEither
接收Function函数,接收一个参数,返回一个新的值;
acceptEither
接收Consumer函数,接收一个参数,没有返回值。
public static void main(String[] args) {
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("执行task1");
return "task1完成";
});
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
System.out.println("执行task2");
return "task2完成";
});
CompletableFuture<String> future = future1.applyToEither(future2, (res) -> res + ",执行task3");
CompletableFuture.allOf(future1, future2, future).join();
}
// 输出
执行task2
task2完成,执行task3
执行task1
并行执行异步任务
allof
public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs);
当我们有多个任务,任务之间没有关联关系,我们可以使用 allOf
方法并行执行,allOf
会等待所有的任务完成后返回。
public static void main(String[] args) {
CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> System.out.println("task1完成"));
CompletableFuture<Void> future2 = CompletableFuture.runAsync(() -> System.out.println("task2完成"));
CompletableFuture.allOf(future1, future2).join();
}
// 输出
task1完成
task2完成
anyof
public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs);
anyOf
方法则不会等待所有的任务都运行完成之后再返回,只要有一个执行完成就会返回。
public static void main(String[] args) {
CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("task1完成");
});
CompletableFuture<Void> future2 = CompletableFuture.runAsync(() -> System.out.println("task2完成"));
CompletableFuture.anyOf(future1, future2).join();
}
// 输出
task2完成
小结
上面介绍了CompletableFuture
的常用方法,根据这些方法,我们来实现上面的需求,根据服务1返回的数据去请求服务4,再将四个服务的数据全部返回。
public class DataService {
private final static ExecutorService executorService = Executors.newFixedThreadPool(3);
public static void main(String[] args) {
List<String> result = new CopyOnWriteArrayList<>();
CompletableFuture<Void> future1 = CompletableFuture.supplyAsync(() -> {
result.add("data from service1");
return "result1";
}, executorService).thenAccept(res -> {
result.add("data from service4 dependent on " + res);
});
CompletableFuture<Void> future2 = CompletableFuture.runAsync(() -> result.add("data from service2"), executorService);
CompletableFuture<Void> future3 = CompletableFuture.runAsync(() -> result.add("data from service3"), executorService);
try {
CompletableFuture.allOf(future1, future2, future3).get(1, TimeUnit.SECONDS);
} catch (InterruptedException | ExecutionException | TimeoutException e) {
// 处理异常
e.printStackTrace();
}
System.out.println(result);
executorService.shutdown();
}
}
// 输出
[data from service1, data from service4 dependent on result1, data from service2, data from service3]
上述代码使用CompletableFuture
实现了该需求,我们来总结一下CompletableFuture
的优点。
- 支持异步函数式编程,实现优雅,代码简洁,易于维护。
- 支持链式任务,任务回调,可以轻松地组织不同任务的运行顺序、规则以及方式。
源码分析
volatile Object result;
volatile Completion stack;
CompletableFuture
中维护了两个属性,result
字段用于存储任务的结果或异常,stack
字段是一个Completion内部类,用于构建一个栈,存储完成时需要执行的回调操作。
runAsync和supplyAsync
runAsync
和supplyAsync
的主要区别在于使用不同的内部类去执行任务。
runAsync
使用AsyncRun
内部类,在完成任务时,使用completeNull
方法将CompletableFuture的结果设置为内部类new AltResult(null)
,结果为null。
而supplyAsync
使用AsyncSupply
内部类,完成任务时,使用completeValue
方法将结果设置为函数式接口Supplier
返回的结果。
最后两者都会调用postComplete
方法,触发回调链上的操作,进行下一任务。
AsyncRun
static final class AsyncRun extends ForkJoinTask<Void>
implements Runnable, AsynchronousCompletionTask {
// ...省略
public void run() {
CompletableFuture<Void> d; Runnable f;
if ((d = dep) != null && (f = fn) != null) {
dep = null; fn = null;
if (d.result == null) {
try {
f.run();
// CAS设置结果为内部类AltResult(null)
d.completeNull();
} catch (Throwable ex) {
d.completeThrowable(ex);
}
}
// 任务结束后,执行所有依赖此任务的其他任务
d.postComplete();
}
}
}
AsyncSupply
static final class AsyncSupply<T> extends ForkJoinTask<Void>
implements Runnable, AsynchronousCompletionTask {
// ...省略
public void run() {
CompletableFuture<T> d; Supplier<T> f;
if ((d = dep) != null && (f = fn) != null) {
dep = null; fn = null;
if (d.result == null) {
try {
// CAS设置结果为任务返回结果
d.completeValue(f.get());
} catch (Throwable ex) {
d.completeThrowable(ex);
}
}
// 任务结束后,执行所有依赖此任务的其他任务
d.postComplete();
}
}
}
postComplete
final void postComplete() {
CompletableFuture<?> f = this; Completion h;
// 循环遍历回调链上的任务,执行回调
while ((h = f.stack) != null ||
(f != this && (h = (f = this).stack) != null)) {
CompletableFuture<?> d; Completion t;
// 从头遍历stack,并更新头元素
if (f.casStack(h, t = h.next)) {
if (t != null) {
// 如果f不是当前CompletableFuture,则将它的头结点压入到当前CompletableFuture的stack中,使树形结构变成链表结构,避免递归层次过深
if (f != this) {
pushStack(h);
continue;
}
// 如果是当前CompletableFuture, 解除头节点与栈的联系
h.next = null;
}
f = (d = h.tryFire(NESTED)) == null ? this : d;
}
}
}
每个CompletableFuture持有一个Completion栈stack, 每个Completion持有一个CompletableFuture -> dep, 如此递归循环下去,是层次很深的树形结构,所以想办法将其变成链表结构。
thenRun
public CompletableFuture<Void> thenRun(Runnable action) {
return uniRunStage(null, action);
}
private CompletableFuture<Void> uniRunStage(Executor e, Runnable f) {
if (f == null) throw new NullPointerException();
CompletableFuture<Void> d = new CompletableFuture<Void>();
// 检查依赖的那个任务是否完成,没有完成返回false,推入栈
if (e != null || !d.uniRun(this, f, null)) {
UniRun<T> c = new UniRun<T>(e, d, this, f);
push(c);
c.tryFire(SYNC);
}
return d;
}
final boolean uniRun(CompletableFuture<?> a, Runnable f, UniRun<?> c) {
Object r; Throwable x;
// 被依赖的任务未完成,结果为null,返回false
if (a == null || (r = a.result) == null || f == null)
return false;
// 被依赖的任务完成了
if (result == null) {
if (r instanceof AltResult && (x = ((AltResult)r).ex) != null)
completeThrowable(x, r);
else
try {
// 判断任务是否能被执行
if (c != null && !c.claim())
return false;
// 执行任务
f.run();
// 设置任务结果
completeNull();
} catch (Throwable ex) {
completeThrowable(ex);
}
}
return true;
}
在 thenRun
方法中,通过 uniRunStage
方法创建了一个新的 CompletableFuture<Void>
对象 d
,然后尝试直接执行当前任务,如果无法直接执行(上一任务未完成),则创建一个 UniRun
对象 c
,表示 thenRun
的回调。
UniRun
static final class UniRun<T> extends UniCompletion<T,Void> {
Runnable fn;
UniRun(Executor executor, CompletableFuture<Void> dep,
CompletableFuture<T> src, Runnable fn) {
super(executor, dep, src); this.fn = fn;
}
final CompletableFuture<Void> tryFire(int mode) {
CompletableFuture<Void> d; // 依赖的任务
CompletableFuture<T> a; // 被依赖任务
// 异步模式(mode = 1),就不判断任务是否结束
if ((d = dep) == null ||
!d.uniRun(a = src, fn, mode > 0 ? null : this))
return null;
dep = null; src = null; fn = null;
// 完成之后的处理
return d.postFire(a, mode);
}
}
postFire
final CompletableFuture<T> postFire(CompletableFuture<?> a, int mode) {
// 存在被依赖任务,且回调链上还有回调任务,执行回调
if (a != null && a.stack != null) {
// mode小于0表示嵌套执行,保证回调链的顺序执行,清除依赖任务的回调链
if (mode < 0 || a.result == null)
a.cleanStack();
else
// 执行依赖任务的完成操作,触发回调
a.postComplete();
}
// 依赖任务完成,且回调链上有任务
if (result != null && stack != null) {
// 嵌套模式,直接返回自身(树 -> 链表,避免过深的递归调用)
if (mode < 0)
return this;
else
// 执行完成操作,触发回调链上的任务
postComplete();
}
return null;
}
cleanStack
// 过滤掉已经死掉的结点
final void cleanStack() {
// q指针从头结点开始,向右移动,p要么为空,要么指向最后一个活着的节点
for (Completion p = null, q = stack; q != null;) {
Completion s = q.next;
// p活着,q指向p,p指向下一节点
if (q.isLive()) {
p = q;
q = s;
}
else if (p == null) {
// q死掉且p为空,去掉头结点,重新开始
casStack(q, s);
q = stack;
}
else {
// q死掉,p节点的next为死节点的下一节点
p.next = s;
if (p.isLive())
q = s;
else {
p = null;
q = stack;
}
}
}
}
allof
public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs) {
return andTree(cfs, 0, cfs.length - 1);
}
static CompletableFuture<Void> andTree(CompletableFuture<?>[] cfs, int lo, int hi) {
CompletableFuture<Void> d = new CompletableFuture<Void>();
// 为空返回空结果
if (lo > hi)
d.result = NIL;
else {
CompletableFuture<?> a, b;
// 找到中间位置
int mid = (lo + hi) >>> 1;
// 构建左子树a和右子树b
if ((a = (lo == mid ? cfs[lo] :
andTree(cfs, lo, mid))) == null ||
(b = (lo == hi ? a : (hi == mid+1) ? cfs[hi] :
andTree(cfs, mid+1, hi))) == null)
throw new NullPointerException();
// 尝试完成任务,无法完成,创建BiRelay对象作为中继
if (!d.biRelay(a, b)) {
BiRelay<?,?> c = new BiRelay<>(d, a, b);
a.bipush(b, c);
// 触发执行
c.tryFire(SYNC);
}
}
return d;
}
为什么要构建二叉树?
假设有一个任务Z(虚拟的,什么都不做),依赖一组任务[A, B, C, D],任务Z完成则表示所有任务完成。如果这组任务是数组结构或者链表结构,需要不断地轮询遍历数组或链表,这是非常消耗性能的。
使用二叉树的话,Z只要保证Z1和Z2都完成了就行,而Z11,Z12则分别保证了A-D任务都完成。动态规划加二叉树,时间复杂度只是logn级别。
参考:https://www.cnblogs.com/aniao/p/aniao_cf.html