一、摘要
在上篇文章中,我们介绍了Future
相关的用法,使用它可以获取异步任务执行的返回值。
我们再次回顾一下Future
相关的用法。
public class FutureTest {
public static void main(String[] args) throws Exception {
long startTime = System.currentTimeMillis();
// 创建一个线程池
ExecutorService executor = Executors.newFixedThreadPool(1);
// 提交任务并获得Future的实例
Future<String> future = executor.submit(new Callable<String>() {
@Override
public String call() throws Exception {
// 执行下载某文件任务,并返回文件名称
System.out.println("thread name:" + Thread.currentThread().getName() + " 开始执行下载任务");
Thread.sleep(200);
return "xxx.png";
}
});
//模拟主线程其它操作耗时
Thread.sleep(300);
// 通过阻塞方式,从Future中获取异步执行返回的结果
String result = future.get();
System.out.println("任务执行结果:" + result);
System.out.println("总共用时:" + (System.currentTimeMillis() - startTime) + "ms");
// 任务执行完毕之后,关闭线程池
executor.shutdown();
}
}
运行结果如下:
thread name:pool-1-thread-1 开始执行下载任务
任务执行结果:xxx.png
总共用时:308ms
如果不采用线程执行,那么总共用时应该会是 200 + 300 = 500 ms,而采用线程来异步执行,总共用时是 308 ms。不难发现,通过Future
和线程池的搭配使用,可以有效的提升程序的执行效率。
但是Future
对异步执行结果的获取并不是很友好,要么调用阻塞方法get()
获取结果,要么轮训调用isDone()
方法是否等于true
来判断任务是否执行完毕来获取结果,这两种方法都不算很好,因为主线程会被迫等待。
因此,从 Java 8 开始引入了CompletableFuture
,它针对Future
做了很多的改进,在实现Future
接口相关功能之外,还支持传入回调对象,当异步任务完成或者发生异常时,自动调用回调对象方法。
下面我们一起来看看CompletableFuture
相关的用法!
二、CompletableFuture 用法介绍
我们还是以上面的例子为例,改用CompletableFuture
来实现,内容如下:
public class FutureTest2 {
public static void main(String[] args) throws Exception {
// 创建异步执行任务
CompletableFuture<String> cf = CompletableFuture.supplyAsync(FutureTest2::download);
// 如果执行成功,回调此方法
cf.thenAccept((result) -> {
System.out.println("任务执行成功,返回结果值:" + result);
});
// 如果执行异常,回调此方法
cf.exceptionally((e) -> {
System.out.println("任务执行失败,原因:" + e.getMessage());
return null;
});
//模拟主线程其它操作耗时
Thread.sleep(300);
}
/**
* 下载某个任务
* @return
*/
private static String download(){
// 执行下载某文件任务,并返回文件名称
System.out.println("thread name:" + Thread.currentThread().getName() + " 开始执行下载任务");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "xxx.png";
}
}
运行结果如下:
thread name:ForkJoinPool.commonPool-worker-1 开始执行下载任务
任务执行成功,返回结果值:xxx.png
可以发现,采用CompletableFuture
类的supplyAsync()
方法进行异步编程,代码上简洁了很多,不需要单独创建线程池。
实际上,CompletableFuture
也使用了线程池来执行任务,部分核心源码如下:
public class CompletableFuture<T> implements Future<T>, CompletionStage<T> {
// 判断当前机器 cpu 可用逻辑核心数是否大于1
private static final boolean useCommonPool = (ForkJoinPool.getCommonPoolParallelism() > 1);
// 默认采用的线程池
// 如果useCommonPool = true,采用 ForkJoinPool.commonPool 线程池
// 如果useCommonPool = false,采用 ThreadPerTaskExecutor 执行器
private static final Executor asyncPool = useCommonPool ?
ForkJoinPool.commonPool() : new ThreadPerTaskExecutor();
// ThreadPerTaskExecutor执行器类
static final class ThreadPerTaskExecutor implements Executor {
public void execute(Runnable r) {
new Thread(r).start(); }
}
// 异步执行任务的方法
public static <U> CompletableFuture<U> supplyAsync(Supplier