模式:用 AsyncExecutor 构建非阻塞并发任务的完整实战指南)
示例工程教程【免费下载链接】java-design-patternsDesign patterns implemented in Java项目地址https://gitcode.com/GitHub_Trending/ja/java-design-patterns点击查看免费下载异步方法调用Asynchronous Method InvocationAMI是一种典型的并发设计模式调用方发起任务后立即返回无需在等待结果期间阻塞自身线程而是在任务完成后通过结果容器、回调或显式等待机制接收执行结果。本文以 java-design-patterns 仓库中的 async-method-invocation 模块为实例从三个核心接口AsyncResult、AsyncCallback、AsyncExecutor到线程级实现ThreadAsyncExecutor再到可运行示例与单元测试系统讲解该模式的设计动机、代码骨架、运行机理与适用场景。读完本文你将能够理解并复刻一套提交即返回、异步取结果、回调通知的最小异步框架并对照 JDK 的FutureTask、CompletableFuture与ExecutorService看清其本质联系。意图让调用线程在等待结果时不被阻塞在传统的同步调用中调用方发起方法调用后必须一直等待直到被调用代码执行完毕并返回结果。如果任务耗时较长如 IO、网络请求或复杂计算调用线程就会被白白占用系统吞吐量与用户体验都会受到影响。异步方法调用模式的核心意图是调用线程在等待任务结果时不会被阻塞。它为多个独立任务提供并行处理的能力并通过以下两种方式之一接收任务结果注册回调Callback任务完成时由执行线程主动通知显式等待await/endProcess在需要结果时再阻塞收集。在 java-design-patterns 仓库中该模式被归类为Concurrency并发类别并打上了Reactive标签体现了它与响应式、事件驱动编程思想的天然亲缘关系详见 localization/zh/async-method-invocation/README.md 的 front matter。通俗解释与真实世界类比用一句话概括异步方法调用会立即开始任务处理并在任务完成之前就返回任务处理的结果稍后再送达调用方。原文给出的真实世界例子非常直观——发射火箭任务指挥官发出发射命令后并不会站在原地干等到火箭升空那一刻。经过一段不确定的时间后火箭要么成功发射要么惨遭失败指挥官在此期间可以继续处理其他指挥事务。维基百科对异步方法调用AMIAsynchronous Method Invocation的经典定义是在多线程计算机编程中异步方法调用是一种设计模式其中在等待被调用代码完成时不会阻塞调用站点而是在执行结果到达时通知调用线程。轮询调用结果是不希望的选项。这个不轮询、等通知的设计正是本模式与朴素忙等待busy-waiting方案的分水岭也是后续await()与回调机制存在的意义。模式骨架三个接口定义异步契约仓库将模式拆解为三个职责清晰的核心接口位于 src/main/java/com/iluwatar/async/method/invocation/1.AsyncResultT异步值的中间容器public interface AsyncResultT { boolean isCompleted(); T getValue() throws ExecutionException; void await() throws InterruptedException; }方法语义依据接口 JavadocisCompleted()返回任务是否已完成或已失败true表示执行结束getValue()获取已完成任务的值若执行失败则抛出ExecutionException携带根因若尚未完成则抛出IllegalStateExceptionawait()阻塞当前线程直到异步任务完成可被中断抛出InterruptedException它是模式中的期货载体调用方拿到的不是最终结果而是一个将来可以兑现结果的凭证。2.AsyncCallbackT任务完成时的回调契约public interface AsyncCallbackT { void onComplete(T value); void onError(Exception ex); }onComplete(T value)任务成功完成后执行value为求值结果onError(Exception ex)任务执行失败时执行ex为失败原因非空。相比轮询回调实现了真正意义上的结果到达时通知是异步模式不阻塞调用站点的关键手段。3.AsyncExecutor异步任务的统一管理者public interface AsyncExecutor { T AsyncResultT startProcess(CallableT task); T AsyncResultT startProcess(CallableT task, AsyncCallbackT callback); T T endProcess(AsyncResultT asyncResult) throws ExecutionException, InterruptedException; }方法行为startProcess(CallableT task)异步启动任务立即返回AsyncResultTstartProcess(CallableT task, AsyncCallbackT callback)同上并在任务完成时执行回调endProcess(AsyncResultT)结束处理必要时阻塞当前线程返回任务求值结果失败时抛出ExecutionException中断时抛出InterruptedException三个接口的分工可以概括为Executor 负责跑Result 负责存Callback 负责叫。实现剖析ThreadAsyncExecutor 与 CompletableResultThreadAsyncExecutor是AsyncExecutor的一个简单实现完整源码见 ThreadAsyncExecutor.java。其核心策略是为每个任务新建一个独立线程public class ThreadAsyncExecutor implements AsyncExecutor { /** Index for thread naming. */ private final AtomicInteger idx new AtomicInteger(0); Override public T AsyncResultT startProcess(CallableT task) { return startProcess(task, null); } Override public T AsyncResultT startProcess(CallableT task, AsyncCallbackT 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(AsyncResultT asyncResult) throws ExecutionException, InterruptedException { if (!asyncResult.isCompleted()) { asyncResult.await(); } return asyncResult.getValue(); } }几个值得注意的实现细节线程命名借助AtomicIntegeridx.incrementAndGet()为每个工作线程生成executor-N名称保证并发环境下索引递增的线程安全也便于从日志中区分不同任务的执行线程。异常捕获边界task.call()抛出的任何Exception都会被捕获并转存到result.setException(ex)从而把任务失败也转化为AsyncResult的一种终态而非让工作线程直接崩溃。无回调重载无回调版本的startProcess直接委托给带回调版本并传入null回调是否存在的判断由CompletableResult内部完成。endProcess的幂等性若任务已完成则直接取值否则先await()再取值多次调用同一结果也不会重复执行任务。内部类CompletableResultT一个简化版 FutureCompletableResult是ThreadAsyncExecutor的私有静态内部类作者在 Javadoc 中明确写道它是FutureTask与CompletableFuture的高度简化版本。它用三态状态机管理任务生命周期static final int RUNNING 1; static final int FAILED 2; static final int COMPLETED 3;state字段声明为volatile保证工作线程写入状态后等待线程能立即看到最新值setValue(T value)设置结果值、置状态为COMPLETED若有回调则执行callback.onComplete(value)最后在lock上notifyAll()唤醒等待者setException(Exception)记录异常、置状态为FAILED若有回调则执行callback.onError(exception)同样唤醒等待者isCompleted()state RUNNING即认为执行结束无论成功或失败getValue()按状态三态分支——COMPLETED返回值、FAILED抛ExecutionException(exception)携带根因、RUNNING抛IllegalStateException(Execution not completed yet)await()在synchronized(lock)块内用while (!isCompleted()) lock.wait()循环等待天然抵抗虚假唤醒spurious wakeup。一个值得强调的线程模型差异源码App.java的 Javadoc 有明确提醒异步结果是在主线程收集的而回调是在工作线程内立即执行的。这意味着回调代码运行在executor-N线程上在使用线程池等场景时要注意回调线程的上下文隔离。可运行示例发射火箭与部署月球漫游车完整的程序入口见 App.java。示例同时展示了收集式与回调式两种接收结果的姿势public static void main(String[] args) throws Exception { // 构造一个将执行异步任务的新执行程序 var executor new ThreadAsyncExecutor(); // 以不同的处理时间开始一些异步任务最后两个使用回调处理程序 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)); // 在当前线程中模拟异步任务正在它们自己的线程中执行 Thread.sleep(350); // 哦兄弟我们在这很辛苦 log(Mission command is sipping coffee); // 等待任务完成 final var result1 executor.endProcess(asyncResult1); final var result2 executor.endProcess(asyncResult2); final var result3 executor.endProcess(asyncResult3); asyncResult4.await(); asyncResult5.await(); // 记录任务结果的日志回调的日志会在回调完成时立刻记录 log(Space rocket result1 launch complete); log(Space rocket result2 launch complete); log(Space rocket result3 launch complete); }配套的两个辅助方法值得留意lazyval(T value, long delayMillis)构造一个先Thread.sleep(delayMillis)再返回value的Callable用来模拟不同耗时的任务10ms~700mscallback(String name)构造一个匿名AsyncCallbackonComplete打印 Deploying lunar rover onError打印失败信息。主线程提交完 5 个任务后只睡了 350ms 就继续喝咖啡而后通过endProcess/await统一收集结果——这正是提交即返回、需要时再取的典型使用范式。程序控制台输出解读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从日志可以清晰地读出两条时间线工作线程时间线executor-1~executor-5各任务按各自的延迟500/300/700/400/600ms独立完成完成顺序与启动顺序无关这正是并行性的直接证据主线程时间线main21:47:08.269时主线程正在喝咖啡直到21:47:08.617之后才统一打印 3 条launch complete。同时注意带回调的任务executor-4、executor-5在自身完成后立即打印 Deploying lunar rover而无需主线程参与——回调在完成瞬间触发佐证了回调执行于工作线程的实现细节。类图与时序图模式的核心类结构与调用时序如下图所示图片来源仓库 async-method-invocation/etc/ 目录Async Method Invocation 类图Async Method Invocation 时序图类图展示出App依赖ThreadAsyncExecutor与AsyncResultThreadAsyncExecutor实现AsyncExecutor并持有内部类CompletableResult实现AsyncResultAsyncCallback作为完成通知的挂载点时序图则直观呈现了主线程提交任务、工作线程求值并触发回调、主线程await收集结果的完整时间线。测试验证源码中的行为契约仓库为ThreadAsyncExecutor提供了覆盖全面且可独立运行的单元测试见 ThreadAsyncExecutorTest.java这些测试本身就是模式行为契约的最佳文档测试用例验证的行为testSuccessfulTaskWithoutCallback成功任务无回调isCompleted()为真任务只执行一次getValue()返回同一对象testSuccessfulTaskWithCallback成功任务带回调onComplete恰好调用一次onError零调用testLongRunningTaskWithoutCallback长耗时任务完成前getValue()抛IllegalStateExceptionawait()后结果可用testLongRunningTaskWithCallback长耗时任务带回调完成前回调零交互完成后onComplete恰好一次testEndProcessendProcess阻塞至完成并返回值且重复调用返回相同结果幂等testNullTask/testNullTaskWithCallback/testNullTaskWithNullCallback传null任务AsyncResult仍非空最终getValue()抛ExecutionException根因为NullPointerException带回调时onError恰好一次、onComplete零调用这些用例印证了实现层的关键事实三态状态机、ExecutionException包裹根因、await的阻塞语义、回调只触发一次以及失败路径与成功路径的对称处理。适用场景与判断标准依据文档当满足以下任一条件时应当考虑使用异步方法调用模式有多个可以并行运行的独立任务——任务之间无数据依赖天然适合并行化需要提高一组顺序任务的性能——把串行等待摊平成并行执行缩短总耗时处理能力或长时间运行的任务数量有限且调用方不应等待任务执行完毕——例如耗时 IO、网络请求、复杂计算等同步化会显著拖累性能或用户体验。更具体地该模式适用于操作无需阻塞程序下一步执行即可继续推进的场景GUI 应用中避免长任务导致界面冻结或无响应Web 应用中需要非阻塞 IO微服务架构中通过消息队列、事件流进行异步通信的服务间协作。真实世界实现与 JDK 并发库的对照文档明确指出Java 标准库本身就有该模式的工业级实现本模块则是帮助理解模式本质的简化教学版FutureTaskFuture的可运行实现任务执行结果可以被异步取出与AsyncResult高度对应CompletableFuture在Future基础上引入回调式编排thenApply、whenComplete等对应AsyncCallback的能力扩展ExecutorService统一管理线程池、提交任务的框架层抽象对应AsyncExecutor的职责基于任务的异步模式Task-based Asynchronous PatternTAP.NET 平台上的同源思想Task/async/await模型。需要说明的是由于并行编程的固有复杂性JDK 的实现远比本示例完备本文模块并没有考虑所有可能的场景而是提供了一个帮助理解该模式的简单版本见App.java类注释因此它非常适合作为学习模式本质的起点再平滑过渡到生产级的并发 API。小结异步方法调用模式用一个AsyncExecutorAsyncResultAsyncCallback的三件套把任务的提交与结果的获取在时间上解耦提交立刻返回、结果按需兑现、完成主动通知。透过 async-method-invocation 模块的源码与测试你可以看到三态状态机、volatile可见性、wait/notifyAll等待机制以及回调线程模型这些底层细节如何落地成不到两百行的可运行框架。当你面对一批独立任务、需要提升整体吞吐或保护调用线程不被长任务拖死时这个模式就是一套直接可用的思路与骨架。赞分享示例工程教程【免费下载链接】java-design-patternsDesign patterns implemented in Java项目地址https://gitcode.com/GitHub_Trending/ja/java-design-patterns点击查看免费下载相关推荐Java 设计模式实战Async Method Invocation 异步方法调用模式在 java-design-patterns 中的实现Java 设计模式实战Async Method Invocation 异步方法调用模式在 java design patterns 中的实现 异步方法调用A示例工程教程java-design-patterns 之异步方法调用Async Method Invocation模式深度解析java design patterns 之异步方法调用Async Method Invocation模式深度解析 异步方法调用Asynchronous示例工程教程深入解析 java-design-patterns 中的 Async Method Invocation异步方法调用模式深入解析 java design patterns 中的 Async Method Invocation异步方法调用模式 本篇文章以 java design示例工程教程上一篇Move Mouse 窗口消失怎么办5 步快速找回消失的界面下一篇AntiDupl.NET 图片去重终极指南3 步快速清理重复照片与相似图片创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考