JDK 21 新特性详解

JDK 21 新特性详解

引言

JDK 21 于 2023 年 9 月正式发布,作为 Java 的 LTS(长期支持)版本,带来了许多重要的新特性和改进。本文将详细介绍 JDK 21 的主要新特性,帮助开发者了解和使用这些新功能。

JDK 21 主要新特性

1. 虚拟线程(Virtual Threads)

虚拟线程是 JDK 21 最重要的特性之一,它极大地简化了并发编程。

// 传统线程使用方式
public void traditionalThreads() {
    for (int i = 0; i < 1000; i++) {
        Thread thread = new Thread(() -> {
            // 模拟 I/O 操作
            try {
                Thread.sleep(1000);
                System.out.println("Thread: " + Thread.currentThread().getName());
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });
        thread.start();
    }
}

// 使用虚拟线程
public void virtualThreads() {
    try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
        for (int i = 0; i < 1000; i++) {
            executor.submit(() -> {
                // 模拟 I/O 操作
                try {
                    Thread.sleep(1000);
                    System.out.println("Virtual Thread: " + Thread.currentThread().getName());
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            });
        }
    }
}

// 更简洁的使用方式
public void simpleVirtualThreads() {
    for (int i = 0; i < 1000; i++) {
        Thread.startVirtualThread(() -> {
            try {
                Thread.sleep(1000);
                System.out.println("Simple Virtual Thread");
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });
    }
}

虚拟线程的核心原理

  • 虚拟线程是 JVM 级别的轻量级线程,不直接对应操作系统线程
  • 应用程序创建大量虚拟线程,而 JVM 使用少量操作系统线程(称为载体线程)来承载它们
  • 当虚拟线程需要执行时,JVM 将其挂载到可用的载体线程上运行
  • 当虚拟线程被阻塞(如等待 I/O)时,JVM 会将其卸载,载体线程可以执行其他虚拟线程
  • 虚拟线程不分配固定的 CPU 时间片,它们在载体线程上运行时遵循载体线程的调度规则

虚拟线程显著减少了操作系统线程的上下文切换开销

  • 传统方式需要创建大量操作系统线程,每个线程都有独立的栈(通常 1MB)
  • 线程间切换需要从用户态切换到内核态,开销较大
  • 虚拟线程使用少量载体线程,大大减少了操作系统线程切换的频率
  • 虚拟线程间的切换完全在 JVM 内部完成,无需操作系统参与

虚拟线程的时间片机制
当虚拟线程的 CPU 时间分片用完时:

  • 虚拟线程会被抢占,就像普通线程一样
  • JVM 会将其从当前的载体线程上卸载
  • 虚拟线程会进入就绪队列等待下一次调度
  • 其他就绪的虚拟线程会被挂载到载体线程上继续执行
  • 这个过程对应用程序是透明的

挂起的虚拟线程状态信息保存与快速恢复

  • 虚拟线程的状态信息(包括局部变量、调用栈等)保存在 JVM 的堆内存中
  • 每个虚拟线程都有自己的栈,但这个栈是堆分配的(默认大小约 1KB),而不是操作系统分配的(1MB)
  • 当虚拟线程被挂起时,其完整的执行状态被保存在堆中的栈结构里
  • 由于状态信息就在 JVM 堆内存中,恢复时无需从用户态获取状态信息,恢复速度极快
  • 这使得虚拟线程可以非常轻量级地创建和销毁,因为不需要操作系统参与栈的管理
  • JVM 内部维护虚拟线程的调度队列和状态信息

虚拟线程的核心特点

  • 虚拟线程由 JVM 管理而非操作系统,具有轻量级(内存占用仅几百字节)、高并发(支持百万级线程)和与传统线程 API 完全兼容的特点
  • 适用于阻塞操作优化场景,但需避免线程本地变量和资源耗尽问题

性能对比测试

public class VirtualThreadPerformanceTest {
    public void performanceComparison() throws InterruptedException {
        int taskCount = 10000;
        
        // 传统线程池方式
        long start1 = System.currentTimeMillis();
        try (ExecutorService executor = Executors.newFixedThreadPool(200)) {
            CountDownLatch latch = new CountDownLatch(taskCount);
            for (int i = 0; i < taskCount; i++) {
                executor.submit(() -> {
                    try {
                        // 模拟 I/O 操作
                        Thread.sleep(100);
                        latch.countDown();
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }
                });
            }
            latch.await();
        }
        long traditionalTime = System.currentTimeMillis() - start1;
        
        // 虚拟线程方式
        long start2 = System.currentTimeMillis();
        try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
            CountDownLatch latch = new CountDownLatch(taskCount);
            for (int i = 0; i < taskCount; i++) {
                executor.submit(() -> {
                    try {
                        // 模拟 I/O 操作
                        Thread.sleep(100);
                        latch.countDown();
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }
                });
            }
            latch.await();
        }
        long virtualTime = System.currentTimeMillis() - start2;
        
        System.out.println("Traditional threads time: " + traditionalTime + "ms");
        System.out.println("Virtual threads time: " + virtualTime + "ms");
        System.out.println("Performance improvement: " + (traditionalTime * 1.0 / virtualTime) + "x");
    }
}

虚拟线程的优势

  • 轻量级:创建和销毁开销极小(每个虚拟线程仅占用几百字节)
  • 高并发:可以轻松创建数百万个虚拟线程
  • 简化编程:无需复杂的线程池管理
  • 减少上下文切换:JVM 自动管理操作系统线程与虚拟线程的映射
  • 更好的资源利用率:避免了大量阻塞的操作系统线程

2. Record Patterns(记录模式)

记录模式扩展了模式匹配的功能,使代码更加简洁和安全。

// 定义记录类
record Point(int x, int y) {}
record Rectangle(Point upperLeft, Point lowerRight) {}

// 传统方式
public void traditionalPattern(Object obj) {
    if (obj instanceof Rectangle) {
        Rectangle rect = (Rectangle) obj;
        Point upperLeft = rect.upperLeft();
        Point lowerRight = rect.lowerRight();
        System.out.println("Rectangle: " + upperLeft.x() + ", " + upperLeft.y() + 
                          " to " + lowerRight.x() + ", " + lowerRight.y());
    }
}

// 使用记录模式
public void recordPattern(Object obj) {
    if (obj instanceof Rectangle(Point upperLeft, Point lowerRight)) {
        System.out.println("Rectangle: " + upperLeft.x() + ", " + upperLeft.y() + 
                          " to " + lowerRight.x() + ", " + lowerRight.y());
    }
}

// 嵌套记录模式
public void nestedRecordPattern(Object obj) {
    if (obj instanceof Rectangle(Point(int x1, int y1), Point(int x2, int y2))) {
        System.out.println("Coordinates: (" + x1 + "," + y1 + ") to (" + x2 + "," + y2 + ")");
    }
}

// 在 switch 中使用
public String analyzeShape(Object obj) {
    return switch (obj) {
        case Point(int x, int y) -> "Point at (" + x + "," + y + ")";
        case Rectangle(Point(int x1, int y1), Point(int x2, int y2)) -> 
            "Rectangle from (" + x1 + "," + y1 + ") to (" + x2 + "," + y2 + ")";
        default -> "Unknown shape";
    };
}

3. Pattern Matching for switch(switch 的模式匹配)

扩展了 switch 语句和表达式,支持类型模式匹配。

// 传统 switch
public String traditionalSwitch(Object obj) {
    return switch (obj.getClass().getSimpleName()) {
        case "String" -> "It's a string: " + obj;
        case "Integer" -> "It's an integer: " + obj;
        case "Double" -> "It's a double: " + obj;
        default -> "Unknown type";
    };
}

// 使用模式匹配的 switch
public String patternSwitch(Object obj) {
    return switch (obj) {
        case String s -> "It's a string: " + s;
        case Integer i -> "It's an integer: " + i;
        case Double d -> "It's a double: " + d;
        case null -> "It's null";
        default -> "Unknown type: " + obj.getClass().getSimpleName();
    };
}

// 复杂模式匹配
public String complexPatternSwitch(Object obj) {
    return switch (obj) {
        case String s when s.length() > 10 -> "Long string: " + s;
        case String s -> "Short string: " + s;
        case Integer i when i > 0 -> "Positive integer: " + i;
        case Integer i -> "Non-positive integer: " + i;
        case List<?> list when list.size() > 5 -> "Large list with " + list.size() + " elements";
        case List<?> list -> "Small list with " + list.size() + " elements";
        default -> "Other: " + obj;
    };
}

4. Sequenced Collections(序列化集合)

为集合类型添加了统一的顺序操作 API。

import java.util.SequencedCollection;
import java.util.SequencedSet;
import java.util.SequencedMap;

public class SequencedCollectionsDemo {
    public void demonstrateSequencedCollections() {
        // SequencedCollection 示例
        SequencedCollection<String> collection = new ArrayList<>();
        collection.add("First");
        collection.add("Second");
        collection.add("Third");
        
        // 获取第一个和最后一个元素
        System.out.println("First: " + collection.getFirst());
        System.out.println("Last: " + collection.getLast());
        
        // 反向视图
        SequencedCollection<String> reversed = collection.reversed();
        System.out.println("Reversed: " + reversed);
        
        // SequencedSet 示例
        SequencedSet<String> set = new LinkedHashSet<>();
        set.add("A");
        set.add("B");
        set.add("C");
        
        System.out.println("Set First: " + set.getFirst());
        System.out.println("Set Last: " + set.getLast());
        
        // SequencedMap 示例
        SequencedMap<String, Integer> map = new LinkedHashMap<>();
        map.put("One", 1);
        map.put("Two", 2);
        map.put("Three", 3);
        
        System.out.println("Map First: " + map.firstEntry());
        System.out.println("Map Last: " + map.lastEntry());
        
        // 反向映射
        SequencedMap<String, Integer> reversedMap = map.reversed();
        System.out.println("Reversed Map: " + reversedMap);
    }
}

5. String Templates(字符串模板)

字符串模板提供了一种安全且高效的方式来创建字符串。

// 字符串模板示例
public class StringTemplatesDemo {
    public void demonstrateStringTemplates() {
        String name = "Alice";
        int age = 30;
        double salary = 50000.50;
        
        // 传统字符串拼接
        String traditional = "Name: " + name + ", Age: " + age + ", Salary: $" + salary;
        
        // 使用字符串模板(预览特性)
        // 注意:这是预览特性,需要启用 --enable-preview
        /*
        String template = STR."Name: \{name}, Age: \{age}, Salary: $\{salary}";
        
        // 复杂表达式
        String complexTemplate = STR."Next year \{name} will be \{age + 1} years old";
        
        // 多行模板
        String multilineTemplate = STR."""
            Employee Details:
            Name: \{name}
            Age: \{age}
            Salary: $\{salary}
            """;
        */
    }
}

6. Scoped Values(作用域值)

Scoped Values 提供了一种在线程及其子线程之间共享不可变数据的安全方式。

// Scoped Values 示例
public class ScopedValuesDemo {
    private static final ScopedValue<String> USER_ID = ScopedValue.newInstance();
    
    public void demonstrateScopedValues() {
        // 在作用域内绑定值
        ScopedValue.where(USER_ID, "user123")
                  .run(() -> {
                      processRequest();
                  });
    }
    
    private void processRequest() {
        // 在任何地方都可以访问作用域值
        String userId = USER_ID.get();
        System.out.println("Processing request for user: " + userId);
        
        // 在虚拟线程中也可以访问
        Thread.startVirtualThread(() -> {
            String userIdInThread = USER_ID.get();
            System.out.println("In virtual thread, user: " + userIdInThread);
        });
    }
}

7. Foreign Function & Memory API(外部函数和内存 API)

这个 API 允许 Java 程序安全地调用外部函数和管理外部内存。

import java.lang.foreign.*;
import java.lang.invoke.MethodHandle;

public class ForeignFunctionDemo {
    public void demonstrateForeignFunction() {
        // 调用 C 标准库函数 strlen
        try (Arena arena = Arena.ofConfined()) {
            // 分配内存
            MemorySegment cString = arena.allocateUtf8String("Hello, World!");
            
            // 查找外部函数
            /*
            SymbolLookup stdlib = SymbolLookup.loaderLookup();
            MethodHandle strlen = Linker.nativeLinker()
                .downcallHandle(
                    stdlib.find("strlen").orElseThrow(),
                    FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS)
                );
            
            // 调用函数
            long len = (long) strlen.invoke(cString);
            System.out.println("String length: " + len);
            */
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

8. 其他重要特性

8.1 Unnamed Patterns and Variables(未命名模式和变量)

// 未命名变量示例
public void unnamedVariables() {
    // 传统方式
    try {
        int result = Integer.parseInt("abc");
    } catch (NumberFormatException e) {
        // 忽略异常,但必须声明变量
        System.out.println("Invalid number format");
    }
    
    // 使用未命名变量(预览特性)
    /*
    try {
        int result = Integer.parseInt("abc");
    } catch (NumberFormatException _) {
        // 使用 _ 作为未命名变量
        System.out.println("Invalid number format");
    }
    */
}

8.2 Unnamed Classes and Instance Main Methods(未命名类和实例主方法)

// 未命名类和实例主方法(预览特性)
/*
void main() {
    System.out.println("Hello from instance main!");
    new MyApplication().run();
}

class MyApplication {
    void run() {
        System.out.println("Running application...");
    }
}
*/

JDK 21 相比 JDK 8 的主要改进

相比 JDK 8,JDK 21 在以下方面有显著改进:

1. 并发性能提升

  • 虚拟线程:极大简化了并发编程,支持百万级并发连接
  • CompletableFuture 增强:提供了更多便捷方法
  • StampedLock 优化:提升了读写锁性能

2. 垃圾回收优化

  • ZGC:低延迟垃圾收集器达到生产就绪状态
  • G1GC 改进:提升了大堆内存的垃圾回收性能
  • Shenandoah GC:并发垃圾收集器持续优化

3. 语法和语言特性

  • 记录类(Records):简化数据载体类的编写
  • 文本块(Text Blocks):更方便地处理多行字符串
  • 模式匹配:switch 和 instanceof 的模式匹配增强
  • 密封类(Sealed Classes):控制类的继承关系

4. 性能和稳定性

  • JIT 编译器优化:提升了代码执行效率
  • 元空间优化:改进了类加载和内存管理
  • 容器感知:更好地支持 Docker 等容器环境

适用场景

JDK 21 适合以下应用场景升级:

  • 需要高吞吐量和低延迟的 Web 应用
  • 高并发的微服务架构
  • 需要处理大量 I/O 操作的应用
  • 对垃圾回收停顿时间敏感的系统
  • 需要现代化语言特性的新项目开发

总结

JDK 21 作为 LTS 版本,带来了许多重要的新特性:

  1. 虚拟线程:简化并发编程,提高应用程序的可伸缩性
  2. 记录模式和模式匹配:使代码更加简洁和安全
  3. 序列化集合:提供统一的顺序操作 API
  4. 字符串模板:安全高效地创建字符串(预览特性)
  5. 作用域值:在线程间安全共享不可变数据
  6. 外部函数和内存 API:与本地代码互操作

这些新特性不仅提升了 Java 语言的表达能力,还改善了开发体验和应用程序性能。建议开发者逐步学习和应用这些新特性,特别是在新项目中充分利用虚拟线程等重要改进来构建高性能的应用程序。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容