JDK 21 是继 JDK 17 之后的又一个长期支持(LTS)版本,于 2023 年 9 月发布。它被誉为 Java 历史上最具变革性的版本之一,特别是虚拟线程的引入,彻底改变了 Java 在高并发领域的编程模型。
相比 JDK 17,JDK 21 不仅在性能上有了质的飞跃(尤其是 GC 和并发能力),还在语法糖(如模式匹配)上更加成熟。本教程将带你深入掌握 JDK 21 的核心特性,通过实战代码体验 Java 的现代化演进。
一、 并发编程的革命:虚拟线程
这是 JDK 21 最核心的亮点(JEP 444)。长期以来,Java 的线程模型是"一对一"的(一个 Java 线程对应一个操作系统线程),这导致在处理大量阻塞 I/O 操作时,系统资源消耗巨大。
1. 什么是虚拟线程?
虚拟线程是轻量级的线程,由 JVM 管理,而非操作系统。
- 资源消耗:一个虚拟线程仅占用约 400 字节的堆内存(而平台线程需要 1MB+)。
- 并发能力:你可以轻松创建数百万个虚拟线程,而不会耗尽内存。
- 编程模型:保持同步阻塞的代码风格,却拥有异步非阻塞的性能。
2. 实战:从线程池到虚拟线程
在 JDK 21 中,你不再需要复杂的线程池配置来处理高并发 I/O。
import java.util.concurrent.Executors;
public class VirtualThreadDemo {
public static void main(String[] args) throws InterruptedException {
// 方式一:直接启动(适用于一次性任务)
Thread.startVirtualThread(() -> {
System.out.println("Running in: " + Thread.currentThread());
}).join();
// 方式二:使用 ExecutorService(推荐,兼容现有代码)
// 注意:不再需要指定线程池大小,JVM 会自动调度
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
executor.submit(() -> {
// 模拟 I/O 阻塞操作
Thread.sleep(1000);
System.out.println("Task done");
});
} // try-with-resources 会自动等待所有任务完成
}
}
性能收益 :在高并发 I/O 密集型场景(如微服务网关、数据库访问),吞吐量可提升 30%-40% ,内存占用降低 50% 以上。
二、 语法糖的进化:模式匹配与 Record
JDK 21 进一步完善了模式匹配,让复杂的类型转换和逻辑判断变得异常简洁。
1. Switch 模式匹配(JEP 441)
JDK 21 允许在 switch 中直接匹配类型,并结合 when 子句进行条件过滤,彻底消除了繁琐的 if-else 和强制类型转换。
static String formatter(Object obj) {
return switch (obj) {
case Integer i when i > 0 -> "正整数: " + i;
case Integer i -> "非正整数: " + i;
case Long l -> "长整型: " + l;
case String s -> "字符串: " + s.toUpperCase();
case null -> "空值";
default -> "未知类型";
};
}
2. Record 模式与解构
配合 Record 类,你可以直接在 if 或 switch 中解构对象属性。
record Point(int x, int y) {}
static void printSum(Object obj) {
// 直接在 instanceof 中解构 x 和 y
if (obj instanceof Point(int x, int y)) {
System.out.println("坐标和: " + (x + y));
}
}
三、 核心库增强:字符串与集合
1. 字符串模板(预览特性)
虽然仍是预览特性,但字符串模板极大地简化了字符串拼接,且比 + 更安全、更易读。
String name = "JDK 21";
int version = 21;
// 使用 STR 处理器,类似 JavaScript 的模板字符串
String info = STR."欢迎来到 \{name} 版本 \{version}!";
2. 有序集合
JDK 21 为集合引入了统一的 SequencedCollection 接口,不再需要记忆 getFirst、peekFirst 等不同方法。
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
// 统一使用 getFirst 和 getLast
System.out.println(list.getFirst()); // A
System.out.println(list.getLast()); // B
四、 性能与底层:分代 ZGC
ZGC 在 JDK 21 中迎来了重大升级------分代 ZGC。
- 原理:将堆内存分为"年轻代"和"老年代"。绝大多数对象都是"朝生夕死"的,分代回收可以极大地提高回收效率。
- 效果 :GC 停顿时间从毫秒级降低到亚毫秒级(通常小于 1ms),且吞吐量损失极小。
- 开启方式 :
-XX:+UseZGC -XX:+ZGenerational
五、 生产环境最佳实践与避坑指南
1. 虚拟线程的"禁区"
- 不要缓存池化 :虚拟线程非常廉价,不要 使用
Executors.newFixedThreadPool来池化虚拟线程。每次任务直接newVirtualThreadPerTaskExecutor即可。 - 避免长时间 CPU 计算:虚拟线程适合 I/O 密集型。如果进行长时间的 CPU 计算,会占用底层的"载体线程",导致其他虚拟线程无法调度。
2. 结构化并发
JDK 21 引入了 StructuredTaskScope(预览),用于管理一组子任务。如果其中一个失败,可以自动取消其他任务,避免"线程泄漏"。
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Future<User> userFuture = scope.fork(() -> findUser());
Future<Order> orderFuture = scope.fork(() -> findOrder());
scope.join(); // 等待所有任务
scope.throwIfFailed(); // 如果有异常则抛出
// 处理结果...
}
3. 升级建议
- Spring Boot 3:如果你使用 Spring Boot 3,它默认支持 JDK 17,但完全兼容 JDK 21。升级 JDK 21 可以让你直接享受到虚拟线程带来的性能红利(Spring Boot 3.2+ 已原生支持虚拟线程)。
六、 实战工具类封装
基于虚拟线程的高并发执行器
封装 Executors.newVirtualThreadPerTaskExecutor(),让旧代码也能轻松享受虚拟线程红利。
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
/**
* 虚拟线程执行器工具类
* 适用于高并发 I/O 密集型任务
*/
public class VirtualExecutor {
/**
* 批量执行任务并等待结果
* @param tasks 任务列表
* @return 结果列表
*/
public static <T> List<T> invokeAll(List<Callable<T>> tasks) throws Exception {
// 使用 try-with-resources 确保所有虚拟线程执行完毕
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
return executor.invokeAll(tasks).stream()
.map(future -> {
try {
return future.get();
} catch (Exception e) {
throw new RuntimeException(e);
}
})
.collect(Collectors.toList());
}
}
// 使用示例
public static void main(String[] args) throws Exception {
List<Callable<String>> tasks = List.of(
() -> { Thread.sleep(1000); return "Task 1"; },
() -> { Thread.sleep(1000); return "Task 2"; }
);
// 并行执行,总耗时约 1 秒,而不是 2 秒
List<String> results = VirtualExecutor.invokeAll(tasks);
System.out.println(results);
}
}