CompletableFuture和Future的区别
想异步执行任务并获取任务的返回值,可以使用Future+线程池组合完成:首先任务实现Callable接口,然后调用Executor.submit()执行任务,最后使用Future.get()获取结果。
public static void main(String[] args) throws Exception {
Clock clock = Clock.systemDefaultZone();
ExecutorService executorService = Executors.newFixedThreadPool(1);
Future<String> task = executorService.submit(() -> {
System.out.println(Thread.currentThread().getName() + ": " + clock.millis());
Thread.sleep(3000);
return "task";
});
System.out.println("result = " + task.get());
System.out.println(Thread.currentThread().getName() + ": " + clock.millis());
executorService.shutdown();
}
Future对于异步线程执行结果的获取,有同步阻塞和轮询查询的方式:
- 通过future.get()同步阻塞获取结果
- 通过future.isDone()不断查询任务是否完成,然后再获取结果
而CompletableFuture实现了Future接口,它提供了一系列回调方法,可以在异步任务完成之后通知回调方法执行。
创建任务
CompletableFuture可以使用runAsync和supplyAsync方法创建异步任务:
- runAsync没有返回值
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(1);
// 默认线程池为ForkJoinPool.commonPool
CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> {
System.out.println(Thread.currentThread().getName());
});
// 自定义线程池
CompletableFuture<Void> future2 = CompletableFuture.runAsync(() -> {
System.out.println(Thread.currentThread().getName());
}, executorService);
executorService.shutdown();
}
// 输出
ForkJoinPool.commonPool-worker-1
pool-1-thread-1
- supplyAsync()方法支持返回值
public static void main(String[] args)throws Exception {
ExecutorService executorService = Executors.newFixedThreadPool(1);
// 默认线程池为ForkJoinPool.commonPool
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> Thread.currentThread().getName());
System.out.println(future1.get());
// 自定义线程池
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> Thread.currentThread().getName(), executorService);
System.out.println(future2.get());
executorService.shutdown();
}
// 输出
ForkJoinPool.commonPool-worker-1
pool-1-thread-1
异步回调
CompletableFuture支持异步回调,可以在完成任务之后,再继续执行回调任务,这个过程是通过通知回调机制自动完成的,不需要等待第一个任务执行完后再手动执行第二个任务。
CompletableFuture有以下回调函数:
- thenRun和thenRunAsync:该方法没有参数值,也没有返回值
- thenAccept和thenAcceptAsync:该方法有参数值,但没有返回值
- thenApply和thenApplyAsync:该方法有参数值,也有返回值
- exceptionally:任务执行异常时执行,异常为参数值
- whenComplete:任务执行完后执行,参数值有两个,第一个是任务的返回值,第二个是异常值
- handle:和whenComplete一样,但有返回值
thenRun和thenRunAsync
thenRun和thenRunAsync回调方法没有参数值,也没有返回值,例如:
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newFixedThreadPool(1);
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
String threadName = Thread.currentThread().getName();
System.out.println("1: " + threadName);
return threadName;
});
future1.thenRun(() -> {
System.out.println("2: " + Thread.currentThread().getName());
});
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
String threadName = Thread.currentThread().getName();
System.out.println("3: " + threadName);
return threadName;
}, executorService);
future1.thenRunAsync(() -> System.out.println("4: " + Thread.currentThread().getName()));
executorService.shutdown();
}
// 输出
1: ForkJoinPool.commonPool-worker-1
2: main
3: pool-1-thread-1
4: ForkJoinPool.commonPool-worker-1
后续分析源码时再讨论执行回调方法的线程是哪一个,建议均使用自定义线程池,可按需控制线程池的大小、队列大小,线程池生命周期等。
thenAccept和thenAcceptAsync
thenAccept和thenAcceptAsync回调方法有参数值,但没有返回值,其参数值是第一个任务执行的结果,例如:
public static void main(String[] args) throws Exception {
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
String threadName = Thread.currentThread().getName();
System.out.println("1: " + threadName);
return threadName;
});
future.thenAccept((lastThreadName) -> {
System.out.println("2: " + Thread.currentThread().getName());
});
}
// 输出
1: ForkJoinPool.commonPool-worker-1
2: main
thenApply和thenApplyAsync
thenApply和thenApplyAsync回调方法有参数值,也有返回值,其参数值是第一个任务执行的结果, 例如:
public static void main(String[] args) throws Exception {
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
String threadName = Thread.currentThread().getName();
System.out.println("1: " + threadName);
return threadName;
});
future.thenApply((lastThreadName) -> {
String threadName = Thread.currentThread().getName();
System.out.println("2: " + threadName);
return threadName;
});
}
// 输出
1: ForkJoinPool.commonPool-worker-1
2: main
exceptionally
exceptionally当任务执行异常时,会执行该回调方法,并且会将异常作为回调方法入参,例如:
public static void main(String[] args) throws Exception {
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
String threadName = Thread.currentThread().getName();
System.out.println("1: " + threadName);
throw new RuntimeException();
});
future.exceptionally((exception) -> {
System.out.println(exception);
return "ERROR";
});
}
// 输出
1: ForkJoinPool.commonPool-worker-1
java.util.concurrent.CompletionException: java.lang.RuntimeException
whenComplete
whenComplete当任务执行完后执行,参数值有两个,第一个是任务的返回值,第二个是异常值,无返回值,但是whenComplete方法返回的CompletableFuture是有返回值的,即上个任务的结果。例如:
public static void main(String[] args) throws Exception {
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
String threadName = Thread.currentThread().getName();
System.out.println("1: " + threadName);
return "I am supplyAsync";
});
CompletableFuture<String> newFuture = future.whenComplete((result, throwable) -> {
String threadName = Thread.currentThread().getName();
System.out.println("2: " + threadName);
});
System.out.println("3: " + newFuture.get());
}
// 输出
1: ForkJoinPool.commonPool-worker-1
2: main
3: I am supplyAsync
handle
handle当任务执行完后执行,参数值有两个,第一个是任务的返回值,第二个是异常值,有返回值,例如:
public static void main(String[] args) throws Exception {
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
String threadName = Thread.currentThread().getName();
System.out.println("1: " + threadName);
return "I am supplyAsync";
});
future.handle((lastThreadName, throwable) -> {
String threadName = Thread.currentThread().getName();
System.out.println("2: " + threadName);
return "I am handle";
});
System.out.println("3: "+future.get());
}
// 输出
1: ForkJoinPool.commonPool-worker-1
2: main
任务组合
CompletableFuture可以组合多个异步任务,组合的方法有如下几个:
And关系
将两个或多个CompletableFuture组合起来,只有都正常执行完了,才会执行某个任务:
- runAfterBoth:组合两个任务,无入参,无返回值
- thenAcceptBoth:组合两个任务,两个任务的执行结果作为方法入参,无返回值
- thenCombine:组合两个任务,两个任务的执行结果作为方法入参,有返回值
public static void main(String[] args) throws Exception {
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "1");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "2");
CompletableFuture<String> future3 = future1.thenCombine(future2, (t1, t2) -> t1 + "_" + t2);
System.out.println(future3.get());
}
// 输出
1_2
- allOf:组合多个任务,没有返回值
public static void main(String[] args) throws Exception {
List<CompletableFuture<Integer>> futureList = new ArrayList<>();
for (int i = 0; i < 4; i++) {
int result = i + 1;
futureList.add(CompletableFuture.supplyAsync(() -> result));
}
// 组合任务
CompletableFuture<Integer> future = CompletableFuture.allOf(futureList.toArray(new CompletableFuture[0]))
.handle((unused, throwable) -> {
Integer result = 0;
for (CompletableFuture<Integer> sonFuture : futureList) {
try {
result += sonFuture.get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
} finally {
sonFuture.cancel(true);
}
}
return result;
});
System.out.println(future.get());
}
// 输出
10
Or关系
将两个CompletableFuture组合起来,只要其中一个执行完了,就会执行某个任务:
- runAfterEither:组合两个任务,无入参,无返回值
- acceptEither:组合两个任务,已经执行完成的任务作为方法入参,无返回值
- applyToEither:组合两个任务,已经执行完成的任务作为方法入参,有返回值
- anyOf:组合多个任务,返回值为Object
public static void main(String[] args) throws Exception {
List<CompletableFuture<Integer>> futureList = new ArrayList<>();
for (int i = 0; i < 4; i++) {
int result = i + 1;
futureList.add(CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(result * 1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return result;
}));
}
CompletableFuture<Object> future = CompletableFuture.anyOf(futureList.toArray(new CompletableFuture[0]));
System.out.println(future.get());
}
// 输出
1