☕ Java 21 新特性

漫画学习手册(完整版)

LTS 重磅升级 · 8大核心特性 · 代码可直接运行 · 图解式学习

💡 一句话总览

Java 21 是继 Java 8、17 之后的又一长期支持版本 (LTS)。 最重磅的特性是虚拟线程彻底改写并发编程,同时带来了 Switch 模式匹配、记录模式、序列集合等 提升开发效率的语法糖;预览特性中的结构化并发、作用域值,更是代表了 Java 并发的未来方向。

🧑‍💻
Java 开发者
📚

专题目录 · 快速跳转

本文件夹共 5 篇手册,点击卡片即可跳转(新标签页打开,本目录不会被覆盖)

🗂️ 子级专题(独立页面)

💡 阅读路线建议: 先通读本页 8 大特性建立整体认知 → 再进入 ① 虚拟线程 与 虚拟线程原理专题 深入底层 → 接着看 结构化并发实战 → 最后用 内存区域版本对比 应对面试提问;需要做 GC 选型或调优时,再看 G1 vs ZGC 深度对比。

1

虚拟线程 Virtual Threads

✅ 正式特性

🎯 解决什么问题?

传统平台线程 = 操作系统线程,一个线程预分配 ~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万个任务瞬间跑完,内存几乎无增长
    }
}
⚠️ 最佳实践: 虚拟线程适合IO密集型场景(网络请求、数据库查询、文件读写); CPU密集型场景请使用平台线程池。虚拟线程不要池化,每个任务新建一个即可,它的创建成本几乎为0。
2

Switch 模式匹配

✅ 正式特性

🎯 解决什么问题?

老版 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]: ...
    }
}
3

记录模式 Record Patterns

✅ 正式特性

🎯 解决什么问题?

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...
    }
}
4

序列集合 Sequenced Collections

✅ 正式特性

🎯 解决什么问题?

以前 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=四
    }
}
5

字符串模板 String Templates

⚠️ 预览特性

🎯 解决什么问题?

告别 + 号拼接、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
6

分代 ZGC

✅ 正式特性

🚀 性能提升

ZGC 是低延迟垃圾回收器,Java 21 引入分代机制后, 暂停时间依然保持亚毫秒级, 同时吞吐量大幅提升,几十到上百 GB 的大内存服务也能稳如磐石。

开启分代 ZGC

java -XX:+UseZGC -XX:+ZGenerational -jar app.jar

核心优势

  • • 最大暂停时间 < 1ms
  • • 支持 TB 级别的堆内存
  • • 分代回收,吞吐量提升 25%+
  • • 大幅降低内存分配停顿
7

结构化并发 Structured Concurrency

⚠️ 预览特性

🎯 解决什么问题?

传统 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();
    }
}
💡 核心价值: 结构化并发让并发代码拥有和串行代码一样的结构化生命周期, 错误传播、取消、清理都是自动的,配合虚拟线程特别适合微服务中多接口聚合、并行查询等场景。
8

作用域值 Scoped Values

⚠️ 预览特性

🎯 解决什么问题?

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 新特性 · 漫画学习手册 | 所有代码示例均可直接运行

↑ 回到顶部专题目录