LTS 重磅升级 · 8大核心特性 · 代码可直接运行 · 图解式学习
💡 一句话总览
Java 21 是继 Java 8、17 之后的又一长期支持版本 (LTS)。 最重磅的特性是虚拟线程彻底改写并发编程,同时带来了 Switch 模式匹配、记录模式、序列集合等 提升开发效率的语法糖;预览特性中的结构化并发、作用域值,更是代表了 Java 并发的未来方向。
本文件夹共 5 篇手册,点击卡片即可跳转(新标签页打开,本目录不会被覆盖)
🗂️ 子级专题(独立页面)
虚拟线程原理与生产实践
核心模型 1:1 vs M:N · 底层实现三大支柱 · 完整调度生命周期 · 为什么切换快 · 常见误区澄清 · 生产最佳实践 · JVM 调优参数大全
virtual-threads.html
打开专题 →
Java 21 结构化并发示例
六大实战场景:微服务扇出调用 · 批量任务超时管控 · 分治计算 · 嵌套任务编排 · 事务型并发 · 多源竞速查询
structured-concurrency.html
打开专题 →
Java 内存区域版本对比
Java 8 / 11 / 17 / 21 四大 LTS 版本运行时数据区差异速记:元空间、GC 演进、字符串常量池、面试开口先答结论
memory-areas.html
打开专题 →
G1 GC vs ZGC 深度对比
内存布局 · 回收流程分镜 · 核心技术底层 · 关键性能指标 · 选型决策建议 · 核心调优参数 · 常见误区澄清 · 生产推荐配置
G1_ZGC.html
打开专题 →
📄 本页目录:Java 21 新特性(8 大特性)
点击后在本页内跳转到对应特性卡片
💡 阅读路线建议: 先通读本页 8 大特性建立整体认知 → 再进入 ① 虚拟线程 与 虚拟线程原理专题 深入底层 → 接着看 结构化并发实战 → 最后用 内存区域版本对比 应对面试提问;需要做 GC 选型或调优时,再看 G1 vs ZGC 深度对比。
🎯 解决什么问题?
传统平台线程 = 操作系统线程,一个线程预分配 ~1MB 栈内存,开 1 万个就会 OOM。 虚拟线程是 JVM 调度的轻量级用户线程,栈内存按需分配, 百万级并发也只占用很小内存,彻底解决 IO 密集型场景的线程瓶颈。
示例1:快速创建与运行虚拟线程
public class VirtualThreadBasic {
public static void main(String[] args) throws InterruptedException {
// 方式1:Thread API 直接创建
Thread v1 = Thread.ofVirtual()
.name("demo-vt-1")
.start(() -> System.out.println("运行在虚拟线程: " + Thread.currentThread().getName()));
// 方式2:Thread.startVirtualThread() 简写
Thread v2 = Thread.startVirtualThread(() ->
System.out.println("简写方式: " + Thread.currentThread().isVirtual()));
v1.join();
v2.join();
// 方式3:自动创建虚拟线程的线程池(生产推荐)
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < 10000; i++) {
int taskId = i;
executor.submit(() -> {
System.out.println("任务" + taskId + " 运行中");
});
}
}
// 1万个任务瞬间跑完,内存几乎无增长
}
}
🎯 解决什么问题?
老版 Switch 只能匹配常量,做类型判断得写一堆 instanceof + 强转。
现在 Switch 直接匹配类型、null、守卫条件,
代码量减少 50%,可读性大幅提升。
示例:类型匹配 + null处理 + 守卫模式
public class SwitchPatternDemo {
public static String format(Object obj) {
return switch (obj) {
// 直接匹配类型并自动强转
case Integer i -> "整数: " + i;
case Long l -> "长整型: " + l;
case String s -> "字符串: " + s;
// 守卫模式:加 when 条件分支
case String s when s.length() > 20 -> "长字符串[" + s.length() + "]: " + s;
// 原生支持 null,再也不怕空指针
case null -> "空值";
default -> "未知类型: " + obj.getClass().getSimpleName();
};
}
public static void main(String[] args) {
System.out.println(format(123)); // 整数: 123
System.out.println(format("hello")); // 字符串: hello
System.out.println(format(null)); // 空值
System.out.println(format("a".repeat(25))); // 长字符串[25]: ...
}
}
🎯 解决什么问题?
Record 记录类本身是不可变数据载体,之前取值还要调用 getter。 现在可以直接解构 Record 中的字段, 配合 Switch 模式匹配,嵌套数据结构处理起来极其优雅。
示例:基础解构 + 嵌套解构 + 类型匹配
// 定义记录类
record Point(int x, int y) {}
record Rectangle(Point topLeft, Point bottomRight) {}
record Circle(Point center, int radius) {}
public class RecordPatternDemo {
public static double calcArea(Object shape) {
return switch (shape) {
// 直接解构 Point,一步拿到 x 和 y
case Point(int x, int y) -> (double) x * y;
// 嵌套解构:直接取出最内层字段
case Rectangle(Point(int x1, int y1), Point(int x2, int y2))
-> Math.abs((double) (x2 - x1) * (y2 - y1));
case Circle(Point(int x, int y), int r) -> Math.PI * r * r;
default -> 0.0;
};
}
public static void main(String[] args) {
System.out.println(calcArea(new Point(10, 20))); // 200.0
System.out.println(calcArea(new Rectangle(
new Point(0, 0), new Point(10, 20)
))); // 200.0
System.out.println(calcArea(new Circle(new Point(0,0), 5))); // 78.539...
}
}
🎯 解决什么问题?
以前 List 有 get(0),但 Set / Map 拿第一个元素得用迭代器,反转集合也很麻烦。
统一了有序集合的操作接口,
首尾增删、反转视图都有了统一方法。
示例:SequencedCollection + SequencedMap
import java.util.*;
public class SequencedDemo {
public static void main(String[] args) {
// ====== SequencedCollection ======
SequencedCollection list = new ArrayList<>(
List.of("A", "B", "C", "D"));
// 首尾元素操作
System.out.println(list.getFirst()); // A
System.out.println(list.getLast()); // D
// 首尾添加
list.addFirst("Z");
list.addLast("E");
System.out.println(list); // [Z, A, B, C, D, E]
// 反转视图(不是副本,是实时视图)
SequencedCollection reversed = list.reversed();
System.out.println(reversed); // [E, D, C, B, A, Z]
// ====== SequencedMap ======
SequencedMap map = new LinkedHashMap<>();
map.put(1, "一");
map.put(2, "二");
map.put(3, "三");
System.out.println(map.firstEntry()); // 1=一
System.out.println(map.lastEntry()); // 3=三
// 头部、尾部插入
map.putFirst(0, "零");
map.putLast(4, "四");
// 反转 Map 视图
SequencedMap reversedMap = map.reversed();
System.out.println(reversedMap.firstEntry()); // 4=四
}
}
🎯 解决什么问题?
告别 + 号拼接、String.format 占位符错位!
直接在字符串里嵌入表达式,类似 JS 模板字符串;
更重要的是支持自定义处理器,可以从根源防止 SQL 注入、XSS 等问题。
示例:基础插值 + 多行模板 + 表达式计算
public class StringTemplateDemo {
public static void main(String[] args) {
String name = "小明";
int age = 22;
// 1. 基础插值:STR."...\{表达式}"
String info = STR."姓名: \{name}, 年龄: \{age}, 明年: \{age + 1}";
System.out.println(info);
// 姓名: 小明, 年龄: 22, 明年: 23
// 2. 支持任意表达式
int x = 10, y = 20;
String math = STR."\{x} + \{y} = \{x + y}";
System.out.println(math); // 10 + 20 = 30
// 3. 多行字符串模板
String html = STR."""
\{name}
年龄: \{age}
出生年份: \{2024 - age}
""";
System.out.println(html);
// 4. FMT 模板:带格式化控制
double price = 99.99;
String fmt = FMT."价格: %.2f\{price} 元";
System.out.println(fmt); // 价格: 99.99 元
}
}
javac --enable-preview --release 21 Demo.java
运行:java --enable-preview Demo
🚀 性能提升
ZGC 是低延迟垃圾回收器,Java 21 引入分代机制后, 暂停时间依然保持亚毫秒级, 同时吞吐量大幅提升,几十到上百 GB 的大内存服务也能稳如磐石。
开启分代 ZGC
java -XX:+UseZGC -XX:+ZGenerational -jar app.jar
核心优势
🎯 解决什么问题?
传统 ExecutorService 中,子任务生命周期和父任务解绑:
一个子任务失败了,其他子任务还在白跑;父任务异常退出,子线程还在后台泄漏。
结构化并发把任务生命周期绑定在代码块中,
父子任务形成结构化关系,错误自动传播、失败自动取消、退出自动清理,
配合虚拟线程是 Java 并发编程的未来范式。
示例1:对比传统写法的痛点
// 传统写法:线程泄漏 + 错误无法传播
public static String oldWay() throws Exception {
ExecutorService executor = Executors.newFixedThreadPool(2);
try {
Future userTask = executor.submit(() -> fetchUser());
Future orderTask = executor.submit(() -> fetchOrder());
// 如果 fetchUser 抛异常,fetchOrder 还会继续运行,浪费资源
// 如果这里代码异常退出,两个任务可能还在后台跑
return userTask.get() + ", " + orderTask.get();
} finally {
executor.shutdown(); // 忘记写就会线程泄漏
}
}
示例2:结构化并发基础用法(失败即取消)
import java.util.concurrent.StructuredTaskScope;
import java.util.concurrent.ExecutionException;
public class StructuredConcurrencyDemo {
// 模拟两个远程 IO 调用
private static String fetchUser() throws InterruptedException {
Thread.sleep(100);
return "用户:小明";
}
private static String fetchOrder() throws InterruptedException {
Thread.sleep(150);
return "订单:12345";
}
public static String structuredWay() throws ExecutionException, InterruptedException {
// try-with-resources 自动管理作用域
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
// fork 子任务,立即返回,不阻塞
var userTask = scope.fork(() -> fetchUser());
var orderTask = scope.fork(() -> fetchOrder());
// 等待所有任务完成;任意一个失败,立即取消所有子任务
scope.join();
scope.throwIfFailed(); // 有失败就抛出异常
// 安全获取结果
return userTask.get() + ", " + orderTask.get();
}
// 离开作用域,所有任务自动清理,绝对不会有线程泄漏
}
public static void main(String[] args) throws Exception {
System.out.println(structuredWay());
// 输出:用户:小明, 订单:12345
}
}
示例3:进阶场景 - 成功即取消(竞速模式)
// 多个渠道查询,只要一个返回结果就全部取消
public static String raceQuery() throws Exception {
try (var scope = new StructuredTaskScope.ShutdownOnSuccess()) {
scope.fork(() -> queryFromDB());
scope.fork(() -> queryFromCache());
scope.fork(() -> queryFromRemote());
scope.join();
// 任意一个成功,立即返回结果并取消其他任务
return scope.result();
}
}
🎯 解决什么问题?
ThreadLocal 是可变的,父子线程继承开销大,
虚拟线程百万级并发下内存占用高,还容易忘记 remove 导致内存泄漏。
Scoped Values 是不可变的、作用域明确的上下文载体,
按需传播、自动清理、内存效率极高,专为虚拟线程场景设计。
示例1:基础用法 - 传递请求上下文
import java.lang.ScopedValue;
public class ScopedValueDemo {
// 定义一个作用域值(类似 ThreadLocal)
private static final ScopedValue REQUEST_ID = ScopedValue.newInstance();
private static final ScopedValue USER_INFO = ScopedValue.newInstance();
public static void main(String[] args) throws InterruptedException {
// 绑定值,在 run 的代码块内有效
ScopedValue.where(REQUEST_ID, "REQ-20240101-001")
.where(USER_INFO, "{id:1001,name:小明}")
.run(() -> processRequest());
// 离开作用域后,值自动消失,不会泄漏
// REQUEST_ID.get(); // 会抛出 NoSuchElementException
}
private static void processRequest() throws InterruptedException {
// 当前线程直接获取
System.out.println("主线程处理 - 请求ID: " + REQUEST_ID.get());
// 虚拟线程自动继承作用域值,无需额外传递
Thread.ofVirtual().start(() -> {
System.out.println("虚拟线程处理 - 请求ID: " + REQUEST_ID.get());
System.out.println("虚拟线程处理 - 用户: " + USER_INFO.get());
}).join();
// 嵌套作用域:临时覆盖,不影响外层
ScopedValue.where(REQUEST_ID, "REQ-SUB-001")
.run(() -> System.out.println("子作用域: " + REQUEST_ID.get()));
// 退出嵌套,恢复原值
System.out.println("恢复外层: " + REQUEST_ID.get());
}
}
📊 Scoped Values vs ThreadLocal 对比
| 特性 | ThreadLocal | Scoped Values |
|---|---|---|
| 可变性 | 可变,可随时 set | 不可变,绑定后不能修改 |
| 作用域 | 线程生命周期,需手动 remove | 代码块作用域,自动清理 |
| 虚拟线程开销 | 高,每个线程都有副本 | 极低,共享不可变值 |
| 线程安全 | 需注意并发修改 | 天然线程安全(不可变) |
| 内存泄漏 | 容易发生 | 不会发生 |
🥇 生产必学(正式特性)
虚拟线程、Switch 模式匹配、记录模式、序列集合、分代 ZGC, 这些都可以直接在生产环境使用,显著提升开发效率和运行性能。
🔮 未来方向(预览特性)
字符串模板、结构化并发、作用域值,代表 Java 未来演进方向, 提前学习可以在正式发布后快速上手,构建更优雅的并发代码。
💻 开发效率提升
模式匹配 + 记录解构 + 序列集合,日常编码少写大量样板代码, 逻辑更清晰,出错概率更低,代码可读性大幅提升。
⚡ 并发能力革命
虚拟线程 + 结构化并发 + 作用域值,三位一体彻底革新 Java 并发模型, 让高并发 IO 场景的编程难度大幅降低,系统吞吐量成倍提升。
Java 21 新特性 · 漫画学习手册 | 所有代码示例均可直接运行