深入理解JDK中的异步编程的利器:Future和CompletableFuture
在日常开发中,异步编程变得更加重要。Java通过Future
和CompletableFuture
提供了强大的工具来处理异步任务。本文将深入探讨这两个类的技术细节,并通过实际案例展示它们的使用。
一、Future
Future
是Java 5引入的一个接口,用于表示异步计算的结果。它允许我们提交一个任务,并在未来的某个时间点获取该任务的结果。
核心方法
public interface Future<V> {
// 尝试取消任务的执行
boolean cancel(boolean mayInterruptIfRunning);
// 判断任务是否被取消
boolean isCancelled();
// 判断任务是否完成
boolean isDone();
// 获取任务的结果,如果任务未完成,则阻塞直到任务完成
V get();
// 在指定时间内获取任务的结果,超时则抛出TimeoutException
V get(long timeout, TimeUnit unit);
}
使用案例
public class FutureTest {
public static void test() throws ExecutionException, InterruptedException {
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<Integer> future = executor.submit(() -> {
Thread.sleep(3000);
return 1;
});
System.out.println("Task submitted");
// 阻塞直到任务完成
System.out.println("Task Result: " + future.get());
executor.shutdown();
}
}
在上面的例子中,我们提交了一个任务,该任务在3秒后返回结果。future.get()
会阻塞当前线程,直到子线程的任务完成并返回结果。
局限性
-
回调缺失:不支持任务完成后的回调逻辑。
-
组合困难:多个Future的依赖关系需手动处理。
-
异常处理:需捕获
InterruptedException和ExecutionException
二、CompletableFuture
CompletableFuture
是Java 8引入的一个类,它实现了Future
接口,并提供了更强大的功能,如任务组合、异常处理等。CompletableFuture
可以手动完成,并且支持回调式的编程风格。
-
链式调用:支持thenApply()、thenAccept()等链式操作。
-
组合操作:支持thenCombine()、allOf()实现多任务协同。
-
异常传播:提供exceptionally()优雅的异常处理方式。
-
响应式特征:支持thenRunAsync()非阻塞回调。
核心方法
public class CompletableFuture<T> implements Future<T>, CompletionStage<T> {
// 异步执行一个没有返回值的任务。使用默认线程池。
public static CompletableFuture<Void> runAsync(Runnable runnable) {
return asyncRunStage(ASYNC_POOL, runnable);
}
// 异步执行一个没有返回值的任务。使用指定线程池。
public static CompletableFuture<Void> runAsync(Runnable runnable,
Executor executor) {
return asyncRunStage(screenExecutor(executor), runnable);
}
// 异步执行一个有返回值的任务。使用默认线程池。
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) {
return asyncSupplyStage(ASYNC_POOL, supplier);
}
// 异步执行一个有返回值的任务。使用指定线程池。
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier,
Executor executor) {
return asyncSupplyStage(screenExecutor(executor), supplier);
}
// 在当前任务完成后,对结果进行处理。
public <U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn) {
return uniApplyStage(null, fn);
}
// 在当前任务完成后,消费结果。
public CompletableFuture<Void> thenAccept(Consumer<? super T> action) {
return uniAcceptStage(null, action);
}
// 在当前任务完成后,执行下一个操作。
public CompletableFuture<Void> thenRun(Runnable action) {
return uniRunStage(null, action);
}
// 处理任务执行过程中的异常。
public CompletableFuture<T> exceptionally(
Function<Throwable, ? extends T> fn) {
return uniExceptionallyStage(null, fn);
}
}
使用案例
1、基本用法
下面示例,我们提交了一个异步任务,该任务在3秒后返回结果。
public class FutureTest {
public static void test() throws ExecutionException, InterruptedException {
// 创建一个异步任务
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
// 模拟业务逻辑耗时
Thread.sleep(3000);
return "Hello, CompletableFuture!";
});
// 获取异步任务的结果
String result = future.join();
System.out.println("异步任务结果:" + result);
}
}
2、超时控制
CompletableFuture.supplyAsync(() -> longTask())
.orTimeout(3, TimeUnit.SECONDS)
.exceptionally(ex -> "Timeout!");
3、自定义线程池
Executor asyncPool = Executors.newFixedThreadPool(4);
CompletableFuture.runAsync(() -> asyncTask(), asyncPool);
4、结果处理 & 异常处理
future.whenComplete((result, throwable) -> {
if (throwable != null) {
log.error("Error occurred", throwable);
}
});
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
System.out.println("执行结束!");
int i = 12 / 0;
return "test";
}).exceptionally(new Function<Throwable, String>() {
@Override
public String apply(Throwable throwable) {
System.out.println(throwable);
return "异常";
}
});
// 执行结束!
// java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
5、组合多个异步任务
通过thenCombine支持将多个异步任务组合在一起执行。
public class CompletableFutureCombineExample {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return 10;
});
CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return 20;
});
CompletableFuture<Integer> combinedFuture = future1.thenCombine(future2, Integer::sum);
System.out.println("Combined Result: " + combinedFuture.get());
}
}
6、回调机制
CompletableFuture
提供了强大的回调机制,允许我们在任务完成时执行特定的操作。以下是一个示例:
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
System.out.println("异步任务执行中...");
return "Hello, CompletableFuture!";
}).thenAccept(result -> {
System.out.println("任务完成,回调结果:" + result);
});
在这个例子中,我们使用了thenAccept()
方法来注册一个回调函数,当异步任务完成时,回调函数会被自动调用。
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
System.out.println("执行结束!");
return "test";
}).thenCompose(new Function<String, CompletionStage<String>>() {
@Override
public CompletionStage<String> apply(String s) {
System.out.println("执行完成!");
return CompletableFuture.supplyAsync(() -> s + " yang");
}
});
在这个例子中,我们使用了thenCompose()
方法来注册一个回调函数,当异步任务完成时,回调函数会被自动调用。
7、链式调用
在这个例子中,我们首先通过 supplyAsync()
创建了一个异步任务,然后使用 thenApply()
对任务结果进行处理,最后使用 thenAccept()
输出最终结果。这种链式调用的方式使得代码更加清晰和易读。
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
System.out.println("Step 1: 获取数据");
return"Hello";
}).thenApply(result -> {
System.out.println("Step 2: 处理数据");
return result + ", CompletableFuture!";
}).thenAccept(result -> {
System.out.println("Step 3: 输出结果");
System.out.println(result);
});
8、任务交互
8.1 applyToEither有返回值
通过该方法,对两个线程Task进行比较。哪个线程获得执行结果的,就执行下一步操作。
CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
int number = new Random().nextInt(10);
System.out.println("第一阶段 start:" + number);
try {
TimeUnit.SECONDS.sleep(number);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第一阶段 end:" + number);
return number;
});
CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {
int number = new Random().nextInt(10);
System.out.println("第二阶段 start:" + number);
try {
TimeUnit.SECONDS.sleep(number);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第二阶段 end:" + number);
return number;
});
future1.applyToEither(future2, new Function<Integer, Object>() {
@Override
public Object apply(Integer integer) {
System.out.println("最快结果:" + integer);
returninteger * 2;
}
});
// 结果
// 第一阶段 start:3
// 第二阶段 start:1
// 第二阶段 end:1
// 最快结果:1
// 第一阶段 end:3
8.2 runAfterEither无返回值
该方法对两个线程比较,有任何一个Task执行完成,就进行下一步操作,不关心各个任务的运行结果。
CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第一阶段:1");
return 1;
});
CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第二阶段:2");
return 2;
});
future1.runAfterEither(future2, new Runnable() {
@Override
public void run() {
System.out.println("已经有一个任务完成了");
}
});
// 结果
// 第一阶段:1
// 已经有一个任务完成了
// 第二阶段:2
8.3 runAfterBoth无返回值
该方法对两个线程比较,两个Task全部执行完成,才进行下一步操作,不关心各个任务的运行结果。
CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第一阶段:1");
return 1;
});
CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第二阶段:2");
return 2;
});
future1.runAfterBoth(future2, new Runnable() {
@Override
public void run() {
System.out.println("上面两个任务都执行完成了");
}
});
// 结果
// 第一阶段:1
// 第二阶段:2
// 上面两个任务都执行完成了
8.4 anyOf有返回值
当其中的任何一个完成时,方法返回。
Random random = new Random();
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(random.nextInt(5));
} catch (InterruptedException e) {
e.printStackTrace();
}
return"1";
});
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(random.nextInt(1));
} catch (InterruptedException e) {
e.printStackTrace();
}
return"2";
});
CompletableFuture<Object> result = CompletableFuture.anyOf(future1, future2);
System.out.println(result.get());
// 结果
// 2
8.5 allOf无返回值
当其中的所有Task完成时,方法返回拿到结果。
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("task1 完成!");
return"task1 完成!";
});
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
System.out.println("task2 完成!");
return"task2 完成!";
});
CompletableFuture<Void> combindFuture = CompletableFuture.allOf(future1, future2);
try {
combindFuture.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
// 结果
// task2 完成!
// task1 完成!
三、总结
Future
和CompletableFuture
是Java中用于异步编程的重要工具。虽然Future
提供了基本的异步任务支持,但它的功能相对有限。CompletableFuture
作为Future
的升级版,提供了更强大的功能,如链式调用、回调机制、异常处理等。通过合理使用CompletableFuture
,我们可以编写出更加高效、清晰和易维护的异步代码。
在实际开发中,CompletableFuture
的应用场景非常广泛,例如异步加载数据、组合多个异步任务、实现复杂的业务流程等。掌握CompletableFuture
的使用方法,将有助于我们更好地应对高并发和性能敏感的应用场景。
希望本文能够帮助你更好地理解和使用Future
和CompletableFuture
。如果你对这些内容有任何疑问或建议,欢迎在评论区留言讨论。
获取更多技术文章和面试秘籍,可以关注VX 公粽号:小鱼修炼笔记