Java 21 新特性概览与实战教程

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 类,你可以直接在 ifswitch 中解构对象属性。

复制代码
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 接口,不再需要记忆 getFirstpeekFirst 等不同方法。

复制代码
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);
    }
}
相关推荐
We་ct2 小时前
JS手撕:性能优化、渲染技巧与定时器实现
开发语言·前端·javascript·面试·性能优化·定时器·性能
柏林以东_2 小时前
java遍历的所有方法及优缺点
java·开发语言·数据结构
taWSw5OjU2 小时前
vue对接海康摄像头-H5player
开发语言·前端·javascript
升职佳兴2 小时前
SQL 进阶3:连续登录问题与 ROW_NUMBER 差值法完整解析
java·数据库·sql
格林威2 小时前
工业相机异常处理实战:断连重连、丢帧检测、超时恢复状态机
开发语言·人工智能·数码相机·计算机视觉·视觉检测·机器视觉·工业相机
Gse0a362g2 小时前
Go - Zerolog使用入门
开发语言·后端·golang
KhalilRuan2 小时前
Burst编译器的底层原理
java·开发语言
Shirley~~2 小时前
力扣hot100:每日温度
开发语言·javascript·ecmascript
Zww08912 小时前
idea配置注释模板
java·ide·intellij-idea