【JDK】-JDK 21 新特性内容

Java JDK 21 核心特性

JDK 21 是 Java 平台的一个重要里程碑,作为最新的长期支持(LTS)版本,它引入了多项革命性的特性,极大地提升了开发效率和应用程序性能。本文档将详细解析 JDK 21 的核心技术更新,帮助开发者快速掌握并应用这些新特性。

1. 版本概览

JDK 21 于 2023 年 9 月正式发布。作为 Oracle 标准支持期限长达 8 年的 LTS 版本,它不仅巩固了先前版本中的预览特性(如虚拟线程、模式匹配),还引入了多项旨在简化编码和提升吞吐量的新功能。

关键总结​:JDK 21 标志着 Java 并发编程模型的重大转变,特别是虚拟线程的正式化,使得高并发应用的构建变得前所未有的简单。

2. JEP 444:虚拟线程(Virtual Threads)

虚拟线程是 JDK 21 中最受瞩目的特性,它是轻量级的线程,旨在大幅减少编写、维护和观察高吞吐量并发应用程序的工作量。

2.1 传统线程与虚拟线程的对比

在引入虚拟线程之前,Java 的 java.lang.Thread 是对操作系统(OS)线程的直接包装。这种"平台线程"存在以下局限:

  • 资源昂贵:创建和维护 OS 线程需要消耗大量的内存和 CPU 资源。
  • 数量受限:受限于 OS 资源,一台机器通常只能运行几千个平台线程。
  • 阻塞代价高:当线程执行 I/O 操作阻塞时,底层的 OS 线程也被占用,导致资源浪费。

虚拟线程则不同,它由 JVM 管理,不与特定的 OS 线程绑定(M:N 调度模型)。

特性 平台线程 (Platform Thread) 虚拟线程 (Virtual Thread)
映射关系 1:1 映射到 OS 线程 M:N 映射到 OS 线程
创建成本 高(毫秒级,MB 级栈内存) 极低(纳秒级,字节级栈内存)
数量上限 几千个 数百万个
调度者 操作系统内核 Java 虚拟机 (JVM)
适用场景 计算密集型任务 I/O 密集型任务

2.2 创建虚拟线程

JDK 21 提供了多种创建虚拟线程的方式,API 设计非常直观。

使用静态构建器
java 复制代码
// 直接启动一个虚拟线程
Thread.ofVirtual().start(() -> {
    System.out.println("Hello from Virtual Thread: " + Thread.currentThread());
});

// 创建但不启动
Thread vThread = Thread.ofVirtual()
    .name("my-virtual-thread")
    .unstarted(() -> {
        System.out.println("Running task...");
    });
vThread.start();
使用 ExecutorService

为了兼容现有的并发代码,Executors 工具类新增了方法来创建基于虚拟线程的线程池。注意,虚拟线程不需要"池化",因为它们的创建成本极低,这里所谓的"线程池"实际上是为每个任务创建一个新的虚拟线程。

java 复制代码
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;

public class VirtualThreadExample {
    public static void main(String[] args) {
        // try-with-resources 结构,自动关闭 ExecutorService
        try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
            for (int i = 0; i < 10000; i++) {
                int taskId = i;
                executor.submit(() -> {
                    // 模拟耗时 I/O 操作
                    try {
                        Thread.sleep(100); 
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }
                    System.out.println("Task " + taskId + " completed.");
                });
            }
        } // executor 在此处自动关闭并等待所有任务完成
    }
}

2.3 最佳实践与注意事项

  1. 不要池化虚拟线程:虚拟线程不仅廉价而且是短暂的,永远不要将它们放入传统的线程池中复用。
  2. **避免长时间的 Pinning(载体线程绑定)**:当在 synchronized 块或方法中执行阻塞操作,或者调用本地方法(JNI)时,虚拟线程会被"钉"在载体线程上,导致无法释放底层的 OS 线程。 ​建议 ​:在虚拟线程中,尽量使用 ReentrantLock 替代 synchronized 以避免 Pinning 问题,除非是在进行纯内存操作。
  3. ThreadLocal 的使用 :虽然虚拟线程支持 ThreadLocal,但由于可能存在数百万个线程,如果每个线程都分配大的对象到 ThreadLocal,可能会导致堆内存溢出。建议使用 Scoped Values(预览特性)作为替代。

3. JEP 431:序列集合(Sequenced Collections)

在 JDK 21 之前,Java 集合框架缺乏一种统一的方式来表示"具有确定出现顺序"的集合。例如,ListDeque 定义了顺序,但 SortedSet 也定义了顺序,然而它们没有共同的父接口来描述这种"有序性"。这也导致了访问第一个或最后一个元素的操作在不同集合中不一致。

3.1 接口层级变化

JDK 21 引入了三个新接口来填补这一空白:

  1. SequencedCollection<E>
  2. SequencedSet<E>
  3. SequencedMap<K,V>

新的继承关系如下:

  • List extends SequencedCollection
  • Deque extends SequencedCollection
  • SortedSet extends SequencedSet
  • LinkedHashSet implements SequencedSet
  • SortedMap extends SequencedMap
  • LinkedHashMap implements SequencedMap

3.2 统一的操作方法

SequencedCollection 接口提供了一组标准方法来处理首尾元素:

方法 描述
void addFirst(E e) 在集合开头添加元素(如果支持)
void addLast(E e) 在集合末尾添加元素(如果支持)
E getFirst() 获取第一个元素
E getLast() 获取最后一个元素
E removeFirst() 移除并返回第一个元素
E removeLast() 移除并返回最后一个元素
SequencedCollection reversed() 返回集合的逆序视图

3.3 代码示例

以下展示了如何在常用的 ArrayListLinkedHashSet 中使用新 API:

java 复制代码
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.SequencedCollection;
import java.util.SequencedSet;

public class SequencedCollectionsDemo {
    public static void main(String[] args) {
        // List 示例
        SequencedCollection<String> list = new ArrayList<>();
        list.addLast("Apple");
        list.addFirst("Banana"); // Banana, Apple
        list.addLast("Orange");  // Banana, Apple, Orange
        
        System.out.println("First: " + list.getFirst()); // Banana
        System.out.println("Last: " + list.getLast());   // Orange
        
        // Set 示例 (LinkedHashSet 保持插入顺序)
        SequencedSet<String> set = new LinkedHashSet<>();
        set.add("One");
        set.add("Two");
        set.addFirst("Zero"); // Zero, One, Two
        
        System.out.println("Original Set: " + set);
        System.out.println("Reversed Set: " + set.reversed()); // Two, One, Zero
    }
}

注意 ​:对于 SortedSet(如 TreeSet),addFirstaddLast 方法会抛出 UnsupportedOperationException,因为排序集合的顺序是由元素比较规则决定的,不能手动指定位置。

4. JEP 441:Switch 模式匹配(Pattern Matching for Switch)

Switch 表达式在 JDK 21 中得到了极大的增强,现在支持类型匹配,这使得编写多态代码变得更加简洁和安全,消除了大量的 if-else 和强制类型转换。

4.1 基本类型匹配

传统的 Switch 只能支持整数、枚举和字符串。现在,case 标签可以包含类型模式。

java 复制代码
static String formatterPatternSwitch(Object obj) {
    return switch (obj) {
        case Integer i -> String.format("int %d", i);
        case Long l    -> String.format("long %d", l);
        case Double d  -> String.format("double %f", d);
        case String s  -> String.format("String %s", s);
        default        -> obj.toString();
    };
}

4.2 Guard 子句(when 关键字)

我们可以在模式匹配中添加布尔表达式来细化匹配逻辑,使用 when 关键字。这避免了在 case 块内部再写 if 语句。

java 复制代码
static void testTriangle(Shape s) {
    switch (s) {
        case Triangle t when t.calculateArea() > 100 -> 
            System.out.println("Large triangle");
        case Triangle t -> 
            System.out.println("Small triangle");
        default -> 
            System.out.println("Unknown shape");
    }
}

4.3 Null 处理

以前,Switch 语句遇到 null 会直接抛出 NullPointerException。现在,可以显式处理 null 情况:

java 复制代码
static void testNull(String s) {
    switch (s) {
        case null -> System.out.println("Oops, it is null!");
        case "Yes", "Y" -> System.out.println("It's Yes");
        case "No", "N" -> System.out.println("It's No");
        default -> System.out.println("Something else");
    }
}

5. JEP 440:记录模式(Record Patterns)

记录模式允许在模式匹配中直接解构 Record 类的值。这一特性与 Switch 模式匹配结合使用时非常强大,可以极大地简化数据导航和处理逻辑。

5.1 什么是 Record 解构?

假设我们有以下 Record 定义:

java 复制代码
record Point(int x, int y) {}
record ColoredPoint(Point p, String color) {}

在 JDK 21 之前,我们需要这样判断并取值:

java 复制代码
Object obj = new Point(10, 20);
if (obj instanceof Point p) {
    int x = p.x();
    int y = p.y();
    // 使用 x 和 y
}

在 JDK 21 中,我们可以直接解构:

java 复制代码
if (obj instanceof Point(int x, int y)) {
    System.out.println("x = " + x + ", y = " + y);
}

5.2 嵌套模式匹配

记录模式真正的威力在于处理嵌套结构。

java 复制代码
static void printColorOfUpperRightPoint(Object obj) {
    // 嵌套解构:直接深入到 ColoredPoint -> Point -> x, y
    if (obj instanceof ColoredPoint(Point(int x, int y), String color)) {
        System.out.println("Color: " + color + ", Position: " + x + ", " + y);
    }
}

还可以结合 var 使用以简化类型声明:

java 复制代码
if (obj instanceof ColoredPoint(Point(var x, var y), var color)) {
    // ...
}

6. JEP 439:分代 ZGC(Generational ZGC)

ZGC(Z Garbage Collector)最初在 JDK 11 作为实验性特性引入,旨在实现低延迟垃圾回收。在 JDK 21 中,ZGC 引入了​分代假说​(Generational Hypothesis),即"大多数对象都是朝生夕死的"。

6.1 性能提升

分代 ZGC 将堆内存划分为​年轻代 ​(Young Generation)和​老年代​(Old Generation)。

  • 更高效的回收:专注于频繁回收年轻代,因为那里充满了垃圾。
  • 更低的 CPU 开销:减少了扫描长寿命对象(老年代)的频率。

6.2 启用方式

在 JDK 21 中,分代 ZGC 尚未默认启用,需要通过命令行参数开启:

bash 复制代码
java -XX:+UseZGC -XX:+ZGenerational ...

性能数据​:根据 Oracle 的测试,分代 ZGC 相比非分代 ZGC,吞吐量提升了 10% 以上,且维持了亚毫秒级的暂停时间。对于大内存应用(数百 GB 甚至 TB 级),这是极佳的选择。

7. 其他值得关注的特性

7.1 Key Encapsulation Mechanism API (JEP 452)

为密钥封装机制(KEM)引入了新的 API,这是一种利用公钥加密来保护对称密钥的高级加密技术,对后量子加密算法的支持至关重要。

7.2 Prepare to Disallow the Dynamic Loading of Agents (JEP 451)

当代理试图动态加载到正在运行的 JVM 时发出警告。这是为了最终默认禁止该操作做准备,以提高默认情况下的安全性。

7.3 String Templates (JEP 430 - 预览特性)

虽然是预览特性,但它非常重要。它允许在字符串中嵌入表达式,比传统的 + 拼接或 String.format 更安全、更易读。

java 复制代码
String name = "Joan";
String info = STR."My name is \{name}";

8. 从旧版本迁移的建议

如果您计划从 Java 8、11 或 17 迁移到 JDK 21,请参考以下步骤:

  1. 依赖升级:确保您的构建工具(Maven/Gradle)和第三方库(如 Spring Boot, Lombok)支持 JDK 21。Spring Boot 3.2+ 对 JDK 21 有很好的支持。
  2. 代码扫描 :使用 jdeprscan 工具检查代码中是否使用了已废弃的 API。
  3. Lombok 兼容性:如果您使用 Lombok,请务必升级到 1.18.30 或更高版本,以修复与新编译器的一些兼容性问题。
  4. 虚拟线程试点:不要试图一次性将所有线程池替换为虚拟线程。建议先在 I/O 密集型的独立模块中尝试,观察性能指标和资源使用情况。

总结

JDK 21 凭借虚拟线程序列集合等特性,不仅解决了 Java 长期以来的痛点,还为云原生应用开发提供了强大的基础设施支持。无论是为了提升并发性能,还是为了享受更现代化的语法糖,升级到 JDK 21 都是一个极具价值的选择。


参考资源

相关推荐
wjs20242 小时前
JavaScript 作用域
开发语言
m0_531237172 小时前
C语言-指针终阶
c语言·开发语言
散峰而望3 小时前
C++ 启程:从历史到实战,揭开命名空间的神秘面纱
c语言·开发语言·数据结构·c++·算法·github·visual studio
易辰君3 小时前
【Python爬虫实战】正则:中文匹配与贪婪非贪婪模式详解
开发语言·爬虫·python
普通网友3 小时前
PHP语言的正则表达式
开发语言·后端·golang
黎雁·泠崖3 小时前
Java常用类核心详解(七):正则表达式 Regex 从入门到实战
java·开发语言·正则表达式
PingdiGuo_guo3 小时前
C++数据类型、变量常量
开发语言·c++
sheji34164 小时前
【开题答辩全过程】以 婚纱影楼管理系统为例,包含答辩的问题和答案
java·eclipse