
从零开始的实战教程 —— 搭建环境到代码运行全程可跟做JDK21.0.11 (Oracle LTS)IDEIDEA Educational 2021.3.1难度入门 → 进阶目录环境准备让 IDEA 正确识别 JDK 21核心概念同步与异步到底有什么区别同步编程实战感受阻塞的代价多线程异步入门Thread 与 RunnableCompletableFuture 深入现代异步编程核心虚拟线程 (JDK 21)轻量级并发新纪元综合实战模拟电商订单处理系统1环境准备在写第一行异步代码之前我们需要确保 IDEA 能正确使用你电脑上的 JDK 21。步骤 1查看 JAVA_HOME 环境变量可以输入java -version javac -version echo %JAVA_HOME%来验证,预计输出java version 21.0.11 2026-04-21 LTS javac 21.0.11 C:\Program Files\Java\latest\jdk-21若不是21版本,可以通过修改环境变量的方式修改操作路径右键「此电脑」→ 属性 → 高级系统设置 → 环境变量在「系统变量」区域找到名为JAVA_HOME的变量点击「编辑」将变量值改为你的路径找到名为Path的系统变量点击「编辑」检查列表中是否有%JAVA_HOME%\bin如果没有点击「新建」添加它如果有旧的 条目选中并删除它连续点击「确定」保存所有窗口步骤 2打开 IDEA准备创建项目IDEA Educational 2021.3.1 发布于 2021 年不会自动识别后来安装的 JDK 21。我们需要在创建项目时手动指定 JDK 路径。打开 IntelliJ IDEA在欢迎界面直接点击New Project新建项目按钮步骤 3配置项目并创建在 New Project 窗口中按以下顺序操作左侧选择Java在Project SDK下拉框中如果看不到 JDK 21点击下拉框 → 选择Add SDK → JDK...在弹出的文件浏览窗口中导航到C:\Program Files\Java\latest\jdk-21文件夹点击 OK现在 Project SDK 应显示为21 - ...不要勾选任何 Additional LibrariesArchetype / Library保持空白点击Next不要勾选Create project from template直接点击Next项目名称填写JavaAsyncDemo项目位置Location选择一个你方便找到的目录例如C:\Users\侯丞\IdeaProjects\JavaAsyncDemo点击Finish注意IDEA 可能弹出一个提示 Project SDK is not defined 或 No SDK。如果出现请确认上一步的 JDK 21 路径选择正确。路径必须是包含bin文件夹的那一层即jdk-21本身不要选到bin里面。步骤 4验证项目 SDK 配置项目创建完成后确认 JDK 版本正确点击菜单File → Project Structure快捷键 CtrlAltShiftS左侧选择Project确认Project SDK显示为21确认Project language level选择为21 - Pattern matching for switch或SDK default点击 OK 关闭窗口步骤 5创建包和主类项目结构创建好后我们来建立代码的组织结构。在左侧 Project 面板中展开JavaAsyncDemo→src右键点击src→New → Package输入包名com.demo按回车右键点击com.demo→New → Java Class输入类名Main按回车IDEA 会自动生成以下代码框架package com.demo; public class Main { public static void main(String[] args) { // 在这里写代码 System.out.println(Hello, Async World!); } }点击右上角绿色三角形运行按钮或按 ShiftF10如果控制台输出Hello, Async World!说明环境配置完全正确。JavaAsyncDemo/ ├── .idea/ ├── src/ │ └── com/ │ └── demo/ │ └── Main.java ← 主入口类 └── JavaAsyncDemo.iml后续所有章节的代码都将写在com.demo包下每个示例新建一个独立的类。2核心概念在写代码之前先理解一个根本问题同步和异步到底是什么意思生活类比同步 排队等你去餐厅点餐。你点了一份炒饭然后站在窗口前一直等什么都做不了直到炒饭做好。拿到炒饭后你才能去点下一杯饮料。特点一件事做完才做下一件。等待期间你被阻塞了。异步 拿号等你去餐厅点餐。你点了一份炒饭服务员给你一个号。你回到座位刷手机。等叫号时再去取餐。等待期间你还可以点饮料、和朋友聊天。特点不需要干等。等待期间可以做别的事等好了再回来处理结果。程序中的同步与异步在程序中等待通常指等待某个耗时操作完成比如从数据库读取数据需要几十毫秒到几秒调用第三方 API网络延迟可能需要几秒读写文件磁盘 I/O复杂计算任务CPU 密集型下面的流程图展示了两种模式的核心区别图 2-1同步 vs 异步执行流程对比同步代码长什么样同步代码就是最常见的一行一行写的代码执行顺序从上到下/ 第1行执行完才执行第2行 String data fetchDataFromDB(); // 卡在这里等数据库返回... String result process(data); // 等上一步完成才能开始 sendEmail(result); // 等上一步完成才能开始 System.out.println(全部完成);如果每个操作需要 2 秒总共就需要 6 秒。而且这 6 秒里主线程什么其他事都做不了。异步代码长什么样异步代码把等待这个动作交给别的线程去做主线程继续往下走/ 把 fetchDB 交给另一个线程去做主线程不等它 CompletableFutureString future CompletableFuture.supplyAsync(() - { return fetchDataFromDB(); }); // 主线程不等继续做别的事 doSomethingElse(); // 等真正需要结果时再等它 String data future.join(); // 此时 fetchDB 可能已经做完了什么时候该用异步场景推荐方式原因简单工具脚本同步代码简单性能不是瓶颈调用多个外部 API异步并行调用总耗时大幅缩短Web 服务器处理请求异步一个请求等待时可以处理其他请求大批量数据处理异步 多线程利用多核 CPU 并行计算学习 / 验证逻辑同步先保证逻辑正确再考虑性能学习建议不要一上来就追求异步。先用同步代码把逻辑写对然后找到性能瓶颈通常是某个等待操作再把它改成异步。这就是从能用到好用的进化过程。3同步编程实战我们先写一段同步代码亲身体会阻塞是什么感受。这会成为理解异步价值的最佳铺垫。创建同步演示类右键com.demo→New → Java Class输入类名SyncDemo输入以下完整代码package com.demo; public class SyncDemo { // 模拟一个耗时操作假装在查数据库 private static String fetchFromDB(String name, int millis) { try { Thread.sleep(millis); // 模拟耗时 } catch (InterruptedException e) { throw new RuntimeException(e); } return name 的数据已获取; } public static void main(String[] args) { long start System.currentTimeMillis(); System.out.println( 同步执行开始 ); // 三个耗时操作依次执行 String r1 fetchFromDB(用户信息, 1000); System.out.println(r1); String r2 fetchFromDB(订单信息, 1000); System.out.println(r2); String r3 fetchFromDB(积分信息, 1000); System.out.println(r3); long end System.currentTimeMillis(); System.out.println( 同步执行结束总耗时: (end - start) ms ); } }运行并观察结果右键SyncDemo类文件 →Run SyncDemo.main()或打开文件后按 ShiftF10 同步执行开始 用户信息 的数据已获取 订单信息 的数据已获取 积分信息 的数据已获取 同步执行结束总耗时: 3032ms 关键发现三个操作每个耗时 1 秒总耗时约 3 秒。因为在同步模式下第二个操作必须等第一个完成才能开始。如果这三个操作之间没有依赖关系那这 2 秒的等待就是浪费。问题分析用下面的图来理解为什么同步模式慢图 3-1同步执行 — 主线程全程被占用主线程的时间线是一条直线没有任何重叠。这就是同步模式的本质串行执行全程阻塞。接下来我们用异步来优化它。4多线程异步入门异步编程最基础的实现方式就是多线程。我们先把上面的同步代码改成多线程版本。方式一继承 Thread 类这是最直观的方式但实际开发中用得较少Java 不支持多重继承。新建类ThreadDemo输入以下代码package com.demo; public class ThreadDemo { private static String fetchFromDB(String name, int millis) { try { Thread.sleep(millis); } catch (InterruptedException e) { throw new RuntimeException(e); } return name 的数据已获取; } // 继承 Thread重写 run 方法 static class FetchTask extends Thread { private String name; private int millis; private String result; public FetchTask(String name, int millis) { this.name name; this.millis millis; } // run 方法里写线程要执行的逻辑 Override public void run() { result fetchFromDB(name, millis); System.out.println(Thread.currentThread().getName() -gt; result); } public String getResult() { return result; } } public static void main(String[] args) throws InterruptedException { long start System.currentTimeMillis(); System.out.println( 多线程执行开始 ); // 创建三个线程还没开始运行 FetchTask t1 new FetchTask(用户信息, 1000); FetchTask t2 new FetchTask(订单信息, 1000); FetchTask t3 new FetchTask(积分信息, 1000); // start() 启动线程三个线程并发执行 t1.start(); t2.start(); t3.start(); // join() 让主线程等待这三个线程完成 t1.join(); t2.join(); t3.join(); long end System.currentTimeMillis(); System.out.println( 多线程执行结束总耗时: (end - start) ms ); } }注意start() 不是 run()调用t1.start()会启动新线程并自动调用 run()。如果你直接写t1.run()那不会创建新线程只是在主线程里普通调用方法 —— 完全失去了多线程的意义。这是新手最常犯的错误。运行结果 多线程执行开始 Thread-0 - 用户信息 的数据已获取 Thread-1 - 订单信息 的数据已获取 Thread-2 - 积分信息 的数据已获取 多线程执行结束总耗时: 1015ms 效果对比同步版耗时约 3000ms多线程版约 1000ms。三个操作并行执行总耗时约等于最慢的那个。这就是异步的价值。方式二实现 Runnable 接口推荐继承 Thread 的方式有个问题Java 只支持单继承如果你的类已经继承了别的类就不能再继承 Thread。Runnable接口解决了这个问题。package com.demo; public class RunnableDemo { private static String fetchFromDB(String name, int millis) { try { Thread.sleep(millis); } catch (InterruptedException e) { throw new RuntimeException(e); } return name 的数据已获取; } public static void main(String[] args) throws InterruptedException { long start System.currentTimeMillis(); System.out.println( Runnable 执行开始 ); // 用数组保存结果 final String[] results new String[3]; // 用 Lambda 创建 Runnable比匿名内部类更简洁 Thread t1 new Thread(() -gt; { results[0] fetchFromDB(用户信息, 1000); System.out.println(results[0]); }); Thread t2 new Thread(() -gt; { results[1] fetchFromDB(订单信息, 1000); System.out.println(results[1]); }); Thread t3 new Thread(() -gt; { results[2] fetchFromDB(积分信息, 1000); System.out.println(results[2]); }); t1.start(); t2.start(); t3.start(); t1.join(); t2.join(); t3.join(); long end System.currentTimeMillis(); System.out.println( 总耗时: (end - start) ms ); } }Thread vs Runnable 对比对比项继承 Thread实现 Runnable继承限制不能再继承其他类可以同时继承其他类返回值需要自己存需要自己存代码简洁度需要写一个类可用 Lambda更简洁线程复用不支持可配合线程池使用推荐程度不推荐推荐线程生命周期每个线程从创建到消亡会经历以下状态图 4-1Java 线程生命周期状态转换Thread 方式的局限无法直接获取返回值run() 方法返回 void需要自己用变量存储结果无法处理异常子线程的异常不会传播到主线程手动管理线程创建和销毁线程开销大没有复用机制这些问题CompletableFuture 全部解决了。这就是下一章的内容。5CompletableFuture 深入CompletableFuture是 Java 8 引入的异步编程工具它能让你像写同步代码一样写异步代码同时获得多线程的性能优势。为什么需要 CompletableFuture回顾上一章 Thread/Runnable 的痛点无法获取返回值 → CompletableFuture 可以无法链式调用 → CompletableFuture 支持 then / whenComplete 等无法组合多个异步任务 → CompletableFuture 支持 allOf / anyOf无法处理异常 → CompletableFuture 支持 exceptionally / handle基本用法supplyAsync新建类CFDemo1package com.demo; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; public class CFDemo1 { private static String fetchFromDB(String name, int millis) { try { Thread.sleep(millis); } catch (InterruptedException e) { throw new RuntimeException(e); } return name 的数据已获取; } public static void main(String[] args) { long start System.currentTimeMillis(); System.out.println( CompletableFuture 基本用法 ); // supplyAsync异步执行有返回值 // 这行代码会立刻返回一个 CompletableFuture 对象 // 真正的 fetchFromDB 在另一个线程里执行 CompletableFuturelt;Stringgt; future CompletableFuture.supplyAsync(() -gt; { return fetchFromDB(用户信息, 1000); }); // 主线程可以继续做其他事 System.out.println(主线程没有等待继续执行...); System.out.println(可以做其他事情...); // 当需要结果时调用 join() 等待会阻塞直到完成 // join() 和 get() 的区别join() 不抛 checked 异常 String result future.join(); System.out.println(获取到结果: result); long end System.currentTimeMillis(); System.out.println(总耗时: (end - start) ms); } }运行结果 CompletableFuture 基本用法 主线程没有等待继续执行... 可以做其他事情... 获取到结果: 用户信息 的数据已获取 总耗时: 1012ms链式调用thenApply / thenAccept / thenRunCompletableFuture 最强大的特性之一是链式调用— 上一步的结果可以自动传给下一步package com.demo; import java.util.concurrent.CompletableFuture; public class CFDemo2 { public static void main(String[] args) { System.out.println( 链式调用演示 ); CompletableFuture.supplyAsync(() -gt; { sleep(500); return 原始数据; }) // thenApply接收上一步的结果返回新值 .thenApply(data -gt; { System.out.println(第一步收到: data); return data 加工后; }) // thenApply 再加工一次 .thenApply(data -gt; { System.out.println(第二步收到: data); return data.toUpperCase(); }) // thenAccept接收结果但不返回新值消费结果 .thenAccept(result -gt; { System.out.println(最终结果: result); }) // thenRun不关心结果只执行一个动作 .thenRun(() -gt; { System.out.println(流程结束收工); }) // join 等待整个链完成 .join(); } private static void sleep(int millis) { try { Thread.sleep(millis); } catch (InterruptedException e) { throw new RuntimeException(e); } } } 链式调用演示 第一步收到: 原始数据 第二步收到: 原始数据 加工后 最终结果: 原始数据 加工后 流程结束收工三个链式方法对比方法接收上一步结果返回新值类比thenApply能能加工流水线thenAccept能不能返回 void消费者thenRun不能不能收尾动作组合多个异步任务这是 CompletableFuture 最实用的场景同时发起多个异步任务等它们全部完成。package com.demo; import java.util.concurrent.CompletableFuture; public class CFDemo3 { private static String fetchFromDB(String name, int millis) { try { Thread.sleep(millis); } catch (InterruptedException e) { throw new RuntimeException(e); } return name 的数据; } public static void main(String[] args) { long start System.currentTimeMillis(); System.out.println( 并行执行多个任务 ); // 同时启动三个异步任务 CompletableFuturelt;Stringgt; f1 CompletableFuture.supplyAsync( () -gt; fetchFromDB(用户信息, 1000)); CompletableFuturelt;Stringgt; f2 CompletableFuture.supplyAsync( () -gt; fetchFromDB(订单信息, 1000)); CompletableFuturelt;Stringgt; f3 CompletableFuture.supplyAsync( () -gt; fetchFromDB(积分信息, 1000)); // allOf等待全部完成 CompletableFuture.allOf(f1, f2, f3).join(); // 此时三个任务都已完成取出结果 System.out.println(f1.join()); System.out.println(f2.join()); System.out.println(f3.join()); long end System.currentTimeMillis(); System.out.println(总耗时: (end - start) ms); // --- anyOf只要任意一个完成就继续 --- System.out.println(\n anyOf 演示 ); CompletableFuturelt;Stringgt; fast CompletableFuture.supplyAsync( () -gt; fetchFromDB(快速服务, 300)); CompletableFuturelt;Stringgt; slow CompletableFuture.supplyAsync( () -gt; fetchFromDB(慢速服务, 2000)); Object firstResult CompletableFuture.anyOf(fast, slow).join(); System.out.println(最先完成: firstResult); } } 并行执行多个任务 用户信息 的数据 订单信息 的数据 积分信息 的数据 总耗时: 1018ms anyOf 演示 最先完成: 快速服务 的数据allOf vs anyOfallOf(f1, f2, f3)— 等待全部完成。适合所有数据都拿到了才能继续的场景anyOf(f1, f2, f3)— 只要任意一个完成就继续。适合多源查询谁先返回用谁的场景比如查多个缓存异常处理异步代码的异常处理和同步代码不同 —— 你不能用普通的 try-catch 包住整个链。CompletableFuture 提供了专门的异常处理方法。package com.demo; import java.util.concurrent.CompletableFuture; public class CFDemo4 { public static void main(String[] args) { System.out.println( 异常处理演示 ); // 方式1: exceptionally — 出异常时提供默认值 String r1 CompletableFuture.supplyAsync(() -gt; { if (true) throw new RuntimeException(数据库连接失败); return 正常数据; }) .exceptionally(ex -gt; { System.out.println(捕获异常: ex.getMessage()); return 降级数据默认值; }) .join(); System.out.println(结果: r1); // 方式2: handle — 无论成功失败都会执行 String r2 CompletableFuture.supplyAsync(() -gt; { throw new RuntimeException(网络超时); }) .handle((result, ex) -gt; { if (ex ! null) { System.out.println(handle 捕获: ex.getMessage()); return 兜底数据; } return result 处理完毕; }) .join(); System.out.println(结果: r2); // 方式3: whenComplete — 类似 finally不能修改结果 CompletableFuture.supplyAsync(() -gt; 成功数据) .whenComplete((result, ex) -gt; { if (ex null) { System.out.println(whenComplete 正常完成: result); } else { System.out.println(whenComplete 出错了: ex.getMessage()); } }) .join(); } }三种异常处理方法对比方法只在异常时执行能修改返回值使用场景exceptionally是能返回默认值提供降级数据handle否成功失败都执行能统一处理成功和失败whenComplete否不能记录日志、清理资源使用自定义线程池默认情况下supplyAsync使用 Java 的ForkJoinPool.commonPool()。但在生产环境中你应该使用自定义线程池来更好地控制并发数。package com.demo; import java.util.concurrent.*; public class CFDemo5 { public static void main(String[] args) { // 创建自定义线程池 // 核心线程数 4最大线程数 8 // 队列容量 100空闲 60 秒回收 ExecutorService executor new ThreadPoolExecutor( 4, // corePoolSize 8, // maxPoolSize 60L, // keepAliveTime TimeUnit.SECONDS, // unit new LinkedBlockingQueuelt;gt;(100), // workQueue new ThreadFactory() { private int count 0; Override public Thread newThread(Runnable r) { return new Thread(r, my-pool- count); } } ); long start System.currentTimeMillis(); // 用自定义线程池执行异步任务 CompletableFuturelt;Stringgt; f1 CompletableFuture.supplyAsync(() -gt; { sleep(1000); return 任务1完成; }, executor); CompletableFuturelt;Stringgt; f2 CompletableFuture.supplyAsync(() -gt; { sleep(1000); return 任务2完成; }, executor); CompletableFuture.allOf(f1, f2).join(); System.out.println(f1.join()); System.out.println(f2.join()); long end System.currentTimeMillis(); System.out.println(总耗时: (end - start) ms); // 记得关闭线程池 executor.shutdown(); } private static void sleep(int millis) { try { Thread.sleep(millis); } catch (InterruptedException e) { throw new RuntimeException(e); } } }重要提醒自定义线程池用完后必须调用shutdown()否则线程池中的线程不会停止程序不会退出。这是初学者经常遇到的程序运行完了但一直不结束的问题。6虚拟线程虚拟线程Virtual Thread是 JDK 21 引入的重磅特性。它让 Java 像 Go 的 goroutine 一样能轻松创建数万个线程而不崩溃。为什么需要虚拟线程传统线程平台线程的问题每个平台线程对应一个操作系统线程创建成本高约 1MB 内存一台普通服务器最多创建几千个平台线程线程多了CPU 在线程切换上的开销也急剧增加虚拟线程的优势由 JVM 管理不直接对应 OS 线程创建成本极低约几 KB可以轻松创建数万个虚拟线程代码写法和传统线程几乎一模一样学习成本低图 6-1平台线程 vs 虚拟线程的承载模型创建虚拟线程的两种方式新建类VirtualThreadDemopackage com.demo; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.stream.IntStream; public class VirtualThreadDemo { public static void main(String[] args) throws InterruptedException { System.out.println( 虚拟线程演示 ); // 方式1: Thread.ofVirtual() 直接创建 Thread vt1 Thread.ofVirtual().start(() -gt; { sleep(500); System.out.println(虚拟线程1 执行完毕 - Thread.currentThread()); }); // 方式2: startVirtualThread (更简洁) Thread vt2 Thread.startVirtualThread(() -gt; { sleep(500); System.out.println(虚拟线程2 执行完毕 - Thread.currentThread()); }); // 等待两个虚拟线程完成 vt1.join(); vt2.join(); System.out.println(\n 批量创建 10000 个虚拟线程 ); long start System.currentTimeMillis(); // 方式3: newVirtualThreadPerTaskExecutor // 这是推荐的方式每个任务分配一个虚拟线程 try (ExecutorService executor Executors.newVirtualThreadPerTaskExecutor()) { // 提交 10000 个任务 var futures IntStream.range(0, 10000) .mapToObj(i -gt; executor.submit(() -gt; { sleep(100); return i; })) .toList(); // 等待所有任务完成 int completed 0; for (var f : futures) { f.join(); completed; } System.out.println(完成数量: completed); } // try-with-resources 自动关闭 executor long end System.currentTimeMillis(); System.out.println(10000 个虚拟线程总耗时: (end - start) ms); // 对比用平台线程创建 10000 个 System.out.println(\n 对比10000 个平台线程 ); long start2 System.currentTimeMillis(); try (ExecutorService executor2 Executors.newCachedThreadPool()) { var futures2 IntStream.range(0, 10000) .mapToObj(i -gt; executor2.submit(() -gt; { sleep(100); return i; })) .toList(); for (var f : futures2) { f.join(); } } long end2 System.currentTimeMillis(); System.out.println(10000 个平台线程总耗时: (end2 - start2) ms); } private static void sleep(int millis) { try { Thread.sleep(millis); } catch (InterruptedException e) { throw new RuntimeException(e); } } }运行结果说明虚拟线程版本10000 个任务在约 100-300ms 内全部完成因为它们是并行的每个只 sleep 100ms平台线程版本耗时会明显更长因为创建 10000 个平台线程会消耗大量内存和 CPU 时间用于线程切换。IDEA 可能显示黄色警告IDEA Educational 2021.3.1 不认识Thread.ofVirtual()和Executors.newVirtualThreadPerTaskExecutor()可能标红或显示警告。这不影响编译和运行只要你选择了 JDK 21 作为项目 SDK。直接运行即可。平台线程 vs 虚拟线程对比对比项平台线程 (传统)虚拟线程 (JDK 21)内存占用约 1MB / 线程约几 KB / 线程建议数量数百到数千数万到数百万创建成本高涉及 OS 调用极低JVM 管理适用场景CPU 密集型计算I/O 密集型网络、数据库代码写法传统 Thread / Runnable完全兼容无需改动引入版本JDK 1.0JDK 21 (预览: JDK 19-20)使用建议虚拟线程的最佳实践I/O 密集型任务用虚拟线程网络请求、数据库查询、文件读写CPU 密集型任务仍用平台线程大量数学计算、加密解密不要池化虚拟线程 —— 用完就扔JVM 会自动回收在虚拟线程里不要使用synchronized改用ReentrantLockJDK 21 对此有优化但 Lock 更安全7综合实战最后我们用一个贴近实际的案例来综合运用前面学到的知识。场景模拟一个电商订单查询系统需要并行调用三个微服务获取数据然后汇总返回。创建综合演示类package com.demo; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicLong; public class OrderSystemDemo { // 模拟三个微服务 /** 模拟用户服务耗时约 500ms */ static String getUserService(String userId) { sleep(500); return 用户[( userId )张三, VIP]; } /** 模拟订单服务耗时约 800ms */ static String getOrderService(String userId) { sleep(800); return 订单[ORD-2024-001, 金额 ¥299.00]; } /** 模拟物流服务耗时约 600ms */ static String getLogisticsService(String orderId) { sleep(600); return 物流[顺丰速运, 预计明天送达]; } // 方式一同步查询 static void syncQuery(String userId) { long start System.currentTimeMillis(); String user getUserService(userId); String order getOrderService(userId); String logistics getLogisticsService(ORD-001); String result user | order | logistics; long elapsed System.currentTimeMillis() - start; System.out.println([同步] result); System.out.println([同步] 耗时: elapsed ms\n); } // 方式二CompletableFuture 并行查询 static void asyncQueryWithCF(String userId) { long start System.currentTimeMillis(); CompletableFuturelt;Stringgt; userFuture CompletableFuture.supplyAsync( () -gt; getUserService(userId)); CompletableFuturelt;Stringgt; orderFuture CompletableFuture.supplyAsync( () -gt; getOrderService(userId)); // 物流查询依赖订单结果先拿到订单号再查物流 CompletableFuturelt;Stringgt; logisticsFuture orderFuture .thenCompose(order -gt; CompletableFuture.supplyAsync( () -gt; getLogisticsService(ORD-001))); // 等待全部完成然后合并结果 CompletableFuture.allOf(userFuture, orderFuture, logisticsFuture) .thenApply(v -gt; { String result userFuture.join() | orderFuture.join() | logisticsFuture.join(); return result; }) .thenAccept(result -gt; { long elapsed System.currentTimeMillis() - start; System.out.println([CompletableFuture] result); System.out.println([CompletableFuture] 耗时: elapsed ms\n); }) .join(); } // 方式三虚拟线程并行查询 static void asyncQueryWithVirtualThread(String userId) throws Exception { long start System.currentTimeMillis(); // 用数组保存结果简单方案适合演示 final String[] user new String[1]; final String[] order new String[1]; final String[] logistics new String[1]; try (ExecutorService executor Executors.newVirtualThreadPerTaskExecutor()) { Futurelt;Stringgt; userF executor.submit(() -gt; { user[0] getUserService(userId); return user[0]; }); Futurelt;Stringgt; orderF executor.submit(() -gt; { order[0] getOrderService(userId); return order[0]; }); // 物流依赖订单先等订单完成 orderF.get(); Futurelt;Stringgt; logisticsF executor.submit(() -gt; { logistics[0] getLogisticsService(ORD-001); return logistics[0]; }); userF.get(); logisticsF.get(); } String result user[0] | order[0] | logistics[0]; long elapsed System.currentTimeMillis() - start; System.out.println([虚拟线程] result); System.out.println([虚拟线程] 耗时: elapsed ms\n); } // Main public static void main(String[] args) throws Exception { String userId U001; System.out.println( 电商订单查询系统 \n); System.out.println(--- 方式一同步查询 ---); syncQuery(userId); System.out.println(--- 方式二CompletableFuture ---); asyncQueryWithCF(userId); System.out.println(--- 方式三虚拟线程 ---); asyncQueryWithVirtualThread(userId); System.out.println( 查询结束 ); } private static void sleep(int millis) { try { Thread.sleep(millis); } catch (InterruptedException e) { throw new RuntimeException(e); } }/code/pre 运行结果分析 电商订单查询系统 --- 方式一同步查询 --- [同步] 用户[(U001)张三, VIP] | 订单[ORD-2024-001, 金额 ¥299.00] | 物流[顺丰速运, 预计明天送达] [同步] 耗时: 1903ms --- 方式二CompletableFuture --- [CompletableFuture] 用户[(U001)张三, VIP] | 订单[ORD-2024-001, 金额 ¥299.00] | 物流[顺丰速运, 预计明天送达] [CompletableFuture] 耗时: 1412ms --- 方式三虚拟线程 --- [虚拟线程] 用户[(U001)张三, VIP] | 订单[ORD-2024-001, 金额 ¥299.00] | 物流[顺丰速运, 预计明天送达] [虚拟线程] 耗时: 1408ms 查询结束 性能对比解读 方式 耗时 原因分析 同步 ~1900ms 用户(500) 订单(800) 物流(600) 串行相加 CompletableFuture ~1400ms 用户和订单并行(max800)物流依赖订单后执行(600)总计 8006001400 虚拟线程 ~1400ms 同样的并行逻辑耗时接近 关键发现 CompletableFuture 和虚拟线程的性能几乎相同因为并行逻辑一样。它们的区别在于 CompletableFuture 更擅长任务编排thenCompose, allOf, anyOf 等适合复杂依赖关系 虚拟线程 更擅长高并发 I/O代码写法更简单直接适合大量并发请求 在实际项目中两者可以结合使用用虚拟线程执行单个异步任务用 CompletableFuture 编排多个任务的依赖关系。 知识总结 图 7-1Java 异步编程知识体系总览