Java 中的异步方法调用模式:使用异步编程提升性能
也称为
- 异步过程调用
异步方法调用设计模式的意图
异步方法调用模式旨在通过允许方法异步调用来增强并发性。这种模式有助于执行并行任务,减少等待时间并提高系统吞吐量。
异步方法调用模式的详细解释,以及现实世界的例子
现实世界的例子
异步方法调用使非阻塞操作成为可能,允许多个进程并发运行。这种模式在需要高可扩展性和性能的应用程序中特别有用,例如 Web 服务器和微服务。
在航天火箭的背景下,异步方法调用模式的类似示例可以在任务控制中心和火箭机载系统之间的通信中看到。当任务控制中心向火箭发送命令调整其轨迹或执行系统检查时,他们不会空闲地等待火箭完成任务并报告回来。相反,任务控制中心继续监控任务的其他方面并管理不同的任务。火箭异步执行命令,并在操作完成后向任务控制中心发送状态更新或结果。这使任务控制能够有效地管理多个并发操作,而不会被任何单个任务阻塞,类似于异步方法调用在软件系统中的工作方式。
通俗地说
异步方法调用启动任务处理并在任务准备就绪之前立即返回。任务处理的结果将在稍后返回给调用者。
维基百科说
在多线程计算机编程中,异步方法调用 (AMI),也称为异步方法调用或异步模式,是一种设计模式,其中调用方在等待被调用代码完成时不会被阻塞。相反,调用线程在收到回复时会收到通知。轮询回复是不希望的选择。
Java 中异步方法调用模式的编程示例
假设需要同时执行多个任务。使用异步方法调用模式,您可以启动这些任务,而无需等待每个任务完成,从而优化资源使用并减少延迟。
在这个例子中,我们正在发射航天火箭并部署月球漫游车。
应用程序演示了异步方法调用模式。该模式的关键部分是AsyncResult
,它是一个用于异步计算值的中间容器,AsyncCallback
,它可以在任务完成后执行,以及AsyncExecutor
,它管理异步任务的执行。
public interface AsyncResult<T> {
boolean isCompleted();
T getValue() throws ExecutionException;
void await() throws InterruptedException;
}
public interface AsyncCallback<T> {
void onComplete(T value);
void onError(Exception ex);
}
public interface AsyncExecutor {
<T> AsyncResult<T> startProcess(Callable<T> task);
<T> AsyncResult<T> startProcess(Callable<T> task, AsyncCallback<T> callback);
<T> T endProcess(AsyncResult<T> asyncResult) throws ExecutionException, InterruptedException;
}
ThreadAsyncExecutor
是AsyncExecutor
的实现。接下来将重点介绍其一些关键部分。
public class ThreadAsyncExecutor implements AsyncExecutor {
@Override
public <T> AsyncResult<T> startProcess(Callable<T> task) {
return startProcess(task, null);
}
@Override
public <T> AsyncResult<T> startProcess(Callable<T> task, AsyncCallback<T> callback) {
var result = new CompletableResult<>(callback);
new Thread(
() -> {
try {
result.setValue(task.call());
} catch (Exception ex) {
result.setException(ex);
}
},
"executor-" + idx.incrementAndGet())
.start();
return result;
}
@Override
public <T> T endProcess(AsyncResult<T> asyncResult)
throws ExecutionException, InterruptedException {
if (!asyncResult.isCompleted()) {
asyncResult.await();
}
return asyncResult.getValue();
}
}
然后我们准备发射一些火箭,看看一切是如何协同工作的。
public static void main(String[] args) throws Exception {
// construct a new executor that will run async tasks
var executor = new ThreadAsyncExecutor();
// start few async tasks with varying processing times, two last with callback handlers
final var asyncResult1 = executor.startProcess(lazyval(10, 500));
final var asyncResult2 = executor.startProcess(lazyval("test", 300));
final var asyncResult3 = executor.startProcess(lazyval(50L, 700));
final var asyncResult4 = executor.startProcess(lazyval(20, 400),
callback("Deploying lunar rover"));
final var asyncResult5 =
executor.startProcess(lazyval("callback", 600), callback("Deploying lunar rover"));
// emulate processing in the current thread while async tasks are running in their own threads
Thread.sleep(350); // Oh boy, we are working hard here
log("Mission command is sipping coffee");
// wait for completion of the tasks
final var result1 = executor.endProcess(asyncResult1);
final var result2 = executor.endProcess(asyncResult2);
final var result3 = executor.endProcess(asyncResult3);
asyncResult4.await();
asyncResult5.await();
// log the results of the tasks, callbacks log immediately when complete
log(String.format(ROCKET_LAUNCH_LOG_PATTERN, result1));
log(String.format(ROCKET_LAUNCH_LOG_PATTERN, result2));
log(String.format(ROCKET_LAUNCH_LOG_PATTERN, result3));
}
以下是程序控制台输出。
21:47:08.227[executor-2]INFO com.iluwatar.async.method.invocation.App-Space rocket<test> launched successfully
21:47:08.269[main]INFO com.iluwatar.async.method.invocation.App-Mission command is sipping coffee
21:47:08.318[executor-4]INFO com.iluwatar.async.method.invocation.App-Space rocket<20>launched successfully
21:47:08.335[executor-4]INFO com.iluwatar.async.method.invocation.App-Deploying lunar rover<20>
21:47:08.414[executor-1]INFO com.iluwatar.async.method.invocation.App-Space rocket<10>launched successfully
21:47:08.519[executor-5]INFO com.iluwatar.async.method.invocation.App-Space rocket<callback> launched successfully
21:47:08.519[executor-5]INFO com.iluwatar.async.method.invocation.App-Deploying lunar rover<callback>
21:47:08.616[executor-3]INFO com.iluwatar.async.method.invocation.App-Space rocket<50>launched successfully
21:47:08.617[main]INFO com.iluwatar.async.method.invocation.App-Space rocket<10>launch complete
21:47:08.617[main]INFO com.iluwatar.async.method.invocation.App-Space rocket<test> launch complete
21:47:08.618[main]INFO com.iluwatar.async.method.invocation.App-Space rocket<50>launch complete
何时在 Java 中使用异步方法调用模式
这种模式非常适合需要有效管理多个并行任务的应用程序。它通常用于处理后台进程、提高用户界面响应能力以及管理异步数据处理等场景。
在以下情况下使用异步方法调用模式
- 当操作不需要在继续执行程序的后续步骤之前完成时。
- 对于资源密集型或耗时的任务,例如 IO 操作、网络请求或复杂计算,其中使操作同步会严重影响性能或用户体验。
- 在 GUI 应用程序中,防止在长时间运行的任务期间出现冻结或无响应。
- 在 Web 应用程序中,用于非阻塞 IO 操作。
Java 中异步方法调用模式的现实应用
许多现代应用程序利用异步方法调用模式,包括处理并发请求的 Web 服务器、微服务架构以及需要密集型后台处理的系统。
- Web 服务器异步处理 HTTP 请求,以提高吞吐量并减少延迟。
- 桌面和移动应用程序使用后台线程执行耗时的操作,而不会阻塞用户界面。
- 微服务架构,其中服务通过消息队列或事件流执行异步通信。
- FutureTask
- CompletableFuture
- ExecutorService
- 基于任务的异步模式
异步方法调用模式的优缺点
虽然这种模式提供了显着的性能优势,但它也增加了错误处理和资源管理的复杂性。正确的实现至关重要,以避免潜在的陷阱,例如竞争条件和死锁。
优点
- 提高响应能力:主线程或应用程序流程保持不被阻塞,这提高了 GUI 应用程序中的用户体验和整体响应能力。
- 更好的资源利用:通过启用并行执行,可以更有效地利用系统资源(例如 CPU 和 IO),从而可能提高应用程序的吞吐量。
- 可扩展性:更容易扩展应用程序,因为任务可以更有效地分布到可用资源中。
缺点
- 复杂性:引入异步操作会使代码库更加复杂,使其更难理解、调试和维护。
- 资源管理:需要仔细管理线程或执行上下文,这会带来开销和潜在的资源耗尽问题。
- 错误处理:异步操作会使错误处理更加复杂,因为异常可能在不同的线程或不同的时间发生。
相关的 Java 设计模式
异步方法调用模式通常与其他设计模式配合良好,例如用于封装请求的命令模式、用于事件处理的观察者模式以及用于管理异步结果的 Promise 模式。
- 命令:异步方法调用可用于实现命令模式,其中命令以异步方式执行。
- 观察者:异步方法调用可用于在主题状态更改时异步通知观察者。
- Promise:AsyncResult 接口可以被认为是一种 Promise,表示可能尚不可用的值。