CompletableFuture(完备的Future)
什么是CompletableFuture
CompletableFuture 和 Future 都是 Java 中用于处理异步编程的类,但是它们的作用略有不同。
Future 是一个接口,代表一个异步计算的结果。它提供了一种等待计算完成并获取结果的方式,以及查询计算是否完成的方法。但是它的功能比较受限,不能直接处理计算完成后的结果,也不能将多个异步计算串联起来执行。
CompletableFuture 是一个类,实现了 Future,并提供了更强大的异步编程功能。它可以将多个异步计算串联起来执行,也可以在异步计算完成后处理其结果。此外,它还提供了各种方法来处理异步计算过程中可能出现的异常和错误情况,并支持合并多个异步计算的结果。
因此,CompletableFuture 比 Future 更为灵活和强大,尤其适用于需要执行多个异步计算并在计算完成后进行后续处理的情况。但是,对于简单的异步计算,Future 也可以提供基本的功能。
特性
- 异步执行:
CompletableFuture.supplyAsync()方法用于异步执行任务,并返回一个CompletableFuture对象,表示该任务的结果。 - 方法链:
thenApplyAsync()方法将一个CompletableFuture对象和一个函数连接起来,以异步方式将其结果转换为另一个值,并返回一个新的CompletableFuture对象。 - 异步消费:
thenAcceptAsync()方法用于以异步方式消费CompletableFuture对象的结果,并在结果可用时执行操作。 - 异常处理:
exceptionally()方法用于处理异步任务执行过程中可能出现的异常情况,并返回一个默认值或另一个CompletableFuture对象。
这个例子展示了如何使用 CompletableFuture 来异步执行任务,并在计算完成后处理其结果。它还展示了如何使用方法链来连接多个异步任务,并使用异常处理机制来处理可能出现的异常情况。
为什么CompletableFuture.supplyAsync()创建异步任务没用线程池
CompletableFuture 可以直接创建异步任务,而不需要使用线程池。
当你使用 CompletableFuture.supplyAsync() 或 CompletableFuture.runAsync() 方法创建一个异步任务时,它们会自动使用 ForkJoinPool.commonPool() 中的线程来执行任务。ForkJoinPool 是一个基于工作窃取算法的线程池,它会根据需要创建和回收线程,以最大限度地利用 CPU 和内存资源。
当然,你也可以通过传递一个 Executor 对象来指定自定义的线程池,以控制异步任务的执行方式和资源消耗。例如,可以使用 Executors.newFixedThreadPool() 方法创建一个固定大小的线程池,并将其传递给 CompletableFuture.supplyAsync() 或 CompletableFuture.runAsync() 方法,以确保异步任务在自己的线程池中执行。
总之,CompletableFuture 提供了灵活的方式来创建和执行异步任务,并且可以自动使用线程池来管理任务的执行。如果需要更多的控制和定制,也可以使用自定义的线程池来执行异步任务。
现在演示同一线程执行异步任务(因为我就只开了一个任务)
public class Main {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 执行一个异步任务
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + " is running");
return "Hello";
});
// 用上面的继续
CompletableFuture<String> future2 = future.thenApplyAsync(s -> {
System.out.println(Thread.currentThread().getName() + " is running");
return s + " world";
});
CompletableFuture<Void> future3 = future2.thenAcceptAsync(s -> {
System.out.println(Thread.currentThread().getName() + " is running");
System.out.println(s);
});
future3.get();
}
}
在这个例子中,我们首先使用 CompletableFuture.supplyAsync() 方法创建了一个异步执行的任务,该任务会返回一个字符串 "Hello"。接着,我们使用 thenApplyAsync() 方法将该任务的返回值 "Hello" 进行处理,将其与字符串 " world" 进行拼接,得到新的字符串 "Hello world"。最后,我们使用 thenAcceptAsync() 方法将处理结果输出到控制台。
需要注意的是,在 CompletableFuture 中,每个方法都返回一个新的 CompletableFuture 实例,表示当前任务的结果或者下一步操作的结果。因此,在调用 thenApplyAsync() 和 thenAcceptAsync() 方法时,我们需要将它们的返回值保存到一个新的变量中,以便后续的操作使用。
同时,由于 CompletableFuture 中的每个方法都是异步执行的,因此在使用 get() 方法等待任务执行结果时,需要注意线程的同步和阻塞问题。如果在主线程中调用 get() 方法,会造成主线程的阻塞,直到任务执行完成并返回结果。因此,在实际使用中,我们可以将 CompletableFuture 的异步执行和结果处理放到一个独立的线程中,从而避免主线程的阻塞和等待。
使用自定义线程池
public class Main {
public static void main(String[] args) throws Exception {
// 创建自定义线程
ExecutorService executor = Executors.newFixedThreadPool(10);
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + " is running");
return "Hello";
}, executor);
CompletableFuture<String> future2 = future.thenApplyAsync(s -> {
System.out.println(Thread.currentThread().getName() + " is running");
return s + " world";
}, executor);
CompletableFuture<Void> future3 = future2.thenAcceptAsync(s -> {
System.out.println(Thread.currentThread().getName() + " is running");
System.out.println(s);
}, executor);
future3.get();
executor.shutdown();
// 等待任务执行完毕
executor.awaitTermination(10, TimeUnit.SECONDS);
}
}
在这个例子中,我们使用 Executors.newFixedThreadPool() 方法创建了一个大小为 10 的线程池,用于执行异步任务。然后,我们将线程池作为参数传递给 CompletableFuture.supplyAsync()、thenApplyAsync() 和 thenAcceptAsync() 方法,以便让它们在指定的线程池中执行异步任务。在执行完成后,我们使用 get() 方法等待任务执行结果。
需要注意的是,在使用自定义线程池时,我们需要手动管理线程池的创建、启动、关闭和等待。在程序结束时,我们需要调用 shutdown() 方法关闭线程池,并使用 awaitTermination() 方法等待所有线程执行完成。这样可以避免线程池中的线程在程序结束时被强制关闭,从而保证程序的正确性和稳定性。
总的来说,CompletableFuture 提供了多种方式来支持异步任务的执行和结果处理,可以根据实际需求选择合适的方式和线程池来达到最佳的并发效果。