CompletableFuture实现了CompletableStage接口和Future接口,前者是对后者的一个扩展,增加了异步回调、流式处理、多个Future组合处理的能力,使Java在处理多任务的协同工作时更加顺畅便利。
先看一下传统线程池执行异步线程,获取线程处理结果的方式。
public static void main(String[] args) throws Exception {
// 创建异步执行任务:
ExecutorService executorService= Executors.newSingleThreadExecutor();
Future<String> childThread = executorService.submit(()->{
System.out.println(Thread.currentThread()+" start,time->"+System.currentTimeMillis());
Thread.sleep(2000);
System.out.println(Thread.currentThread()+" exit,time->"+System.currentTimeMillis());
return "子线程结束";
});
System.out.println("main thread start,time->"+System.currentTimeMillis());
//等待子任务执行完成,如果已完成则直接返回结果
System.out.println("child thread result->"+childThread.get());
System.out.println("main thread exit,time->"+System.currentTimeMillis());
executorService.shutdown();
}
执行结果如下
子线程是异步执行的,主线程休眠等待子线程执行完成,子线程执行完成后唤醒主线程,主线程获取任务执行结果后退出。
很多博客说使用不带等待时间限制的get方法时,如果子线程执行异常了会导致主线程长期阻塞,这其实是错误的,子线程执行异常时其异常会被捕获,然后修改任务的状态为异常结束并唤醒等待的主线程,get方法判断任务状态发生变更,就终止等待了,并抛出异常。这时主线程拿到抛出的异常,如果没做特殊处理,则同样会抛出异常终止程序,而不是一直阻塞在那里。
通过源码也可以看出,get方法会对异步线程的状态进行判断,获取线程结果并抛出线程中的异常。
将代码改造如下
public static void main(String[] args) throws Exception {
// 创建异步执行任务:
ExecutorService executorService= Executors.newSingleThreadExecutor();
Future<String> childThread = executorService.submit(()->{
System.out.println(Thread.currentThread()+" start,time->"+System.currentTimeMillis());
Thread.sleep(2000);
if(true){
throw new Exception("抛出异常咯");
}
System.out.println(Thread.currentThread()+" exit,time->"+System.currentTimeMillis());
return "子线程结束";
});
System.out.println("main thread start,time->"+System.currentTimeMillis());
//等待子任务执行完成,如果已完成则直接返回结果
//如果执行任务异常,则get方法会把之前捕获的异常重新抛出
try{
System.out.println("child thread result->"+childThread.get());
}catch (Exception e){
throw e;
}finally {
executorService.shutdown();
}
System.out.println("main thread exit,time->"+System.currentTimeMillis());
}
执行结果如下
这里解释一下为什么要catch住之后再抛出,这样做的目的是为了在finally中shutdown线程池。
线程池内部有一个类似于死循环的方法,这个循环是非守护线程(用户线程),而JVM对于非守护线程,如果不终止的话,程序是不会结束的,main方法就是非守护线程。
所以如果不shutdown线程池,main方法即使因为抛出异常而结束了,但你会发现程序任然在运行,如下所示(将finally中的语句注释掉即可得到下面的运行结果)