✨Java 21 新特性

644 阅读23分钟

官网:openjdk.org/projects/jd…

JDK 21 于 2023 年 9 月 19 日 发布,这是一个非常重要的版本,里程碑式。

JDK21 是 LTS(长期支持版),至此为止,目前有 JDK8、JDK11、JDK17 和 JDK21 这四个长期支持版了。

JDK 21 共有 15 个新特性:

1、JEP 430:字符串模板(预览)

String Templates(字符串模板) 目前仍然是 JDK 21 中的一个预览功能。

String Templates 提供了一种更简洁、更直观的方式来动态构建字符串。通过使用占位符${},我们可以将变量的值直接嵌入到字符串中,而不需要手动处理。在运行时,Java 编译器会将这些占位符替换为实际的变量值。并且,表达式支持局部变量、静态/非静态字段甚至方法、计算结果等特性。

核心改进:通过模板处理器(如 STRFMTRAW)安全嵌入表达式,支持自定义处理器。
场景:避免 SQL 注入、JSON/XSS 转义问题。
示例扩展

// 数学表达式模板
String result = STR."计算: \{2 + 3 * 4}"; // 输出: 计算: 14
// 多行模板
String json = STR."""
{
    "name": "\{name}",
    "version": \{version}
}
""";
// 自定义模板处理器(转义 HTML)
StringTemplate.Processor<String, RuntimeException> HTML = st -> 
    st.interpolate().replace("<", "&lt;").replace(">", "&gt;");
String safeHtml = HTML."<script>\{userInput}</script>";

2、JEP 431:序列集合(Sequenced Collections)

新增接口

  • SequencedCollection:添加 addFirstremoveLast 等方法
  • SequencedSet:确保元素唯一性
  • SequencedMap:提供 firstEntrypollLastEntry 等方法

序列化集合提供了处理集合的第一个和最后一个元素以及反向视图(与原始集合相反的顺序)的简单方法。
集合适配示例

List<String> list = new ArrayList<>(List.of("B", "C"));
SequencedCollection<String> seqList = list;
seqList.addFirst("A");  // list 变为 [A, B, C]
seqList.removeLast();   // list 变为 [A, B]
LinkedHashMap<String, Integer> map = new LinkedHashMap<>();
SequencedMap<String, Integer> seqMap = map;
seqMap.putFirst("Key1", 1);  // 插入到 Map 开头
seqMap.pollLastEntry();       // 移除最后一个条目

3、JEP 439:分代式 ZGC(Generational ZGC)

**分代ZGC(Generational Z Garbage Collector)**是JDK 21引入的一项革命性特性,旨在进一步提升Java应用程序的性能和稳定性。ZGC最初作为实验性功能在JDK 11中发布,后在JDK 15中升级为生产级功能,设计目标是支持高达16TB的堆大小,同时保持亚毫秒级的暂停时间。在JDK 21中,Oracle引入了分代ZGC,通过为年轻对象和老对象维护单独的代,进一步提高了应用程序的性能。

核心特性

  • 分代管理:将堆划分为年轻代和老年代,分别管理不同生命周期的对象。
  • 并发处理:大部分工作在应用程序线程运行时完成,仅在短时间内暂停这些线程。
  • 染色指针:使用染色指针技术来存储额外的、只供收集器或虚拟机本身使用的数据,如对象的哈希码、分代年龄和锁记录等。
  • 内存屏障优化:优化内存屏障的开销,减少不必要的扫描和标记操作。

性能优势

  • 高吞吐量:通过更频繁地收集年轻对象,提高了垃圾回收效率。
  • 低延迟:最大停顿时间优化至亚毫秒级。
  • 可扩展性:支持处理不同规模的堆。
  • 更低的分配停滞风险:减少所需的堆内存开销和垃圾收集的CPU开销。

使用方法:
要使用分代ZGC,需要在启动Java应用程序时指定相应的JVM参数,如-XX:+UseZGC-XX:+ZGenerational。此外,还可以调整其他与ZGC相关的配置参数,如最大堆大小和最小堆大小,以进一步优化应用程序的性能。
JDK21 中对 ZGC 进行了功能扩展,增加了分代 GC 功能。不过,默认是关闭的,需要通过配置打开:

# 传统 ZGC(非分代)
java -XX:+UseZGC -Xmx16g ...
# 分代 ZGC(Java 21 默认启用分代)
java -XX:+UseZGC -XX:+ZGenerational -Xmx16g ...

与旧版本对比

非分代ZGC

  • 暂停时间:不超过1毫秒。
  • 堆大小:从几百MB到许多TB。
  • 适用场景:适用于需要低延迟和高可扩展性的应用。

分代ZGC

  • 暂停时间:优化至亚毫秒级。
  • 堆大小:支持超过16TB的堆大小。
  • 适用场景:适用于需要更高性能和更低延迟的应用。

分代ZGC通过更频繁地收集年轻对象,进一步优化了内存管理,提高了应用程序的响应性能。与非分代ZGC相比,分代ZGC在大多数用例中都成为更好的解决方案。

4、JEP 440:记录模式(Record Patterns)

Java 21引入了记录模式(Record Patterns),这是对Java 14中引入的记录类型(Record)的进一步扩展。记录模式旨在简化和标准化对记录类实例的解构,使程序员能够更方便、安全地访问和操作记录类的组件。以下是详细说明、代码示例及与旧版本的对比。

详细说明

  1. 目的
    • 简化和标准化对记录类实例的解构。
    • 提高代码的可读性和安全性。
    • 支持模式匹配,特别是在instanceofswitch语句中。
  2. 语法
    • 记录模式使用简洁的语法来匹配和解构记录类型。
    • 通过编译时检查确保访问的正确性,避免错误的字段访问或类型不匹配问题。
  3. 特点
    • 简洁性:代码更加简洁明了,减少了冗长的类型检查和属性访问代码。
    • 可读性:语法更加直观易懂,使得代码更加清晰易读。
    • 灵活性:支持对记录类型的属性进行匹配和解构,灵活处理不同的对象类型。
    • 安全性:通过编译时检查确保访问的正确性,避免错误的字段访问或类型不匹配问题。

代码示例

** 定义记录类型**

record Point(int x, int y) {}
record Circle(Point center, double radius) {}

2. 使用记录模式进行解构

private static void processShape(Object shape) {
    if (shape instanceof Circle(Point p, double r)) { // 解构嵌套记录
        System.out.printf("圆心坐标 (%d,%d), 半径 %.2f%n", p.x(), p.y(), r);
    }
}

与旧版本的对比

  1. 简化代码:
    • 在Java 16 及之前版本中,获取shape对象中的pr值需要五行代码:
static void oldProcessShape(Object shape) {
    if (shape instanceof Circle) {
        Circle c = (Circle) shape;  // 需要显式转换
        Point p = c.center();       // 需要调用访问器方法
        double r = c.radius();
        System.out.printf("圆心坐标 (%d,%d), 半径 %.2f%n", 
            p.x(), p.y(), r);
    }
}
  • 在Java 21中,只需两行代码即可完成:
if (shape instanceof Circle(Point p, double r)) { // 解构嵌套记录
    System.out.printf("圆心坐标 (%d,%d), 半径 %.2f%n", p.x(), p.y(), r);
}
  1. 嵌套记录类型:
    • 在处理嵌套记录类型时,Java 21提供了更简洁的解构方式。例如:
static void processShape(Object shape) {
    if (shape instanceof Circle(Point p, double r)) { // 解构嵌套记录
        System.out.printf("圆心坐标 (%d,%d), 半径 %.2f%n", p.x(), p.y(), r);
    }
}
  1. 模式匹配:
    • 记录模式可以与switch语句结合使用,实现更强大的模式匹配功能。例如:
// 在 switch 中使用
static String describe(Object obj) {
    return switch (obj) {
        case Point(int x, int y) -> "点坐标: (" + x + "," + y + ")";
        case Circle(Point p, double r) -> "圆形半径: " + r;
        default -> "未知形状";
    };
}
  1. 模式匹配:
// 结合泛型记录
record Box<T>(T content) {}

static void checkBox(Box<String> box) {
    if (box instanceof Box<String>(var s)) {
        System.out.println("字符串内容: " + s.toUpperCase());
    }
}

// 带守卫条件的模式
static void guardedPattern(Object obj) {
    if (obj instanceof Point(int x, int y) && x == y) {
        System.out.println("坐标点在 y=x 直线上");
    }
}

Java 21的记录模式为Java编程语言带来了更强大的功能和灵活性,特别是在处理复杂数据结构时。通过简化代码、提高可读性和安全性,记录模式显著提升了开发效率和代码质量。

5、JEP 441:switch的模式匹配

Java 21中,模式匹配得到了进一步增强,特别是在switch表达式中的应用。模式匹配允许在switch语句中进行更复杂的匹配和操作,使得代码更加简洁和直观。Java 21中的模式匹配不仅限于instanceof,还包括switch表达式的新能力。通过模式匹配,开发者可以更清晰地表达复杂的条件逻辑,减少代码冗余。

以下是一个使用模式匹配的switch表达式的示例代码:

public class PatternMatchingDemo {
    public static void main(String[] args) {
        Object obj = "Hello, Java!";
        String result = switch (obj) {
            case String s -> "String: " + s;
            case Integer i -> "Integer: " + i;
            default -> "Unknown type";
        };
        System.out.println(result); // String: Hello, Java!
    }
}

在这个示例中,switch表达式根据obj的类型执行不同的操作,代码简洁且易于理解。

与旧版本的对比

  • Java 14:引入了switch表达式预览版,允许使用箭头语法(->)来返回值,并支持多个case标签。
  • Java 16instanceof模式匹配成为永久性特性,允许在if语句中使用模式匹配。
  • Java 17switch模式匹配成为永久性特性,无需启用预览功能即可使用。
  • Java 21:进一步增强了switch模式匹配,支持更多类型和更复杂的模式,如记录模式(Record Patterns)。

通过这些增强,Java 21的模式匹配功能更加强大和灵活,提高了代码的清晰度、可靠性和可读性。

6、JEP 442:外部函数和内存 API(第三次预览)

目标:替代 JNI,提供安全高效的外部内存和函数调用。

Java 程序可以通过该 API 与 Java 运行时之外的代码和数据进行互操作。通过高效地调用外部函数(即 JVM 之外的代码)和安全地访问外部内存(即不受 JVM 管理的内存),该 API 使 Java 程序能够调用本机库并处理本机数据,而不会像 JNI 那样危险和脆弱。

外部函数和内存 API 在 Java 17 中进行了第一轮孵化,由 JEP 412 提出。Java 18 中进行了第二次孵化,由JEP 419 提出。Java 19 中是第一次预览,由 JEP 424 提出。JDK 20 中是第二次预览,由 JEP 434 提出。JDK 21 中是第三次预览,由 JEP 442 提出。

// 调用 C 标准库的 strlen
try (Arena arena = Arena.ofConfined()) {
    MemorySegment str = arena.allocateUtf8String("Hello");
    long len = Linker.nativeLinker().downcallHandle(
        SymbolLookup.loadLib().find("strlen").get(),
        FunctionDescriptor.of(JAVA_LONG, ADDRESS)
    ).invoke(str);
}

7、JEP 443:未命名模式和变量(预览)

未命名模式和变量使得我们可以使用下划线 _ 表示未命名的变量以及模式匹配时不使用的组件,旨在提高代码的可读性和可维护性。

未命名变量的典型场景是 try-with-resources 语句、 catch 子句中的异常变量和for循环。当变量不需要使用的时候就可以使用下划线 _代替,这样清晰标识未被使用的变量。

try (var _ = ScopedContext.acquire()) {
    // No use of acquired resource
}
try { ... }
catch (Exception _) { ... }
catch (Throwable _) { ... }

for (int i = 0, _ = runOnce(); i < arr.length; i++) {
    ...
}

未命名模式是一个无条件的模式,并不绑定任何值。未命名模式变量出现在类型模式中。

if (r instanceof ColoredPoint(_, Color c)) { ... c ... }

switch (b) {
    case Box(RedBall _), Box(BlueBall _) -> processBox(b);
    case Box(GreenBall _)                -> stopProcessing();
    case Box(_)                          -> pickAnotherBox();
}

8、JEP 444:虚拟线程(Virtual Threads)

8.1、虚拟线程的定义和优势

  • 定义:虚拟线程(Virtual Threads)是Java 21中引入的一种轻量级线程实现,旨在提高并发性能和资源利用率。与传统的平台线程(Platform Threads)不同,虚拟线程由JVM内部管理和调度,而不是操作系统内核管理。
  • 优势
    • 性能提升:虚拟线程减少了线程的创建和销毁开销,避免了操作系统线程的上下文切换,从而提高了系统的吞吐量和响应速度。
    • 资源节约:每个虚拟线程仅占用1KB的内存,启动时间仅需1微秒,大大减少了资源消耗。
    • 简化编程:虚拟线程与传统的Thread API高度兼容,只需引入少数组件即可编写高性能的异步代码,降低了开发复杂度。
  • 缺点
    • 不适用于计算密集型任务: 虚拟线程适用于 I/O 密集型任务,但不适用于计算密集型任务,因为密集型计算始终需要 CPU 资源作为支持。
    • 与某些第三方库不兼容: 虽然虚拟线程设计时考虑了与现有代码的兼容性,但某些依赖平台线程特性的第三方库可能不完全兼容虚拟线程。

8.2、虚拟线程的工作原理

  • 调度器:JDK 21中的虚拟线程调度器是一个基于FIFO模式的ForkJoinPool,可以通过设置启动参数进行调整。
  • 切换机制:当虚拟线程执行到IO操作或阻塞操作时,会自动切换到其他虚拟线程执行,避免了当前线程的等待,从而提高了并发处理能力。

8.3、 虚拟线程与平台线程的区别

  • 管理方式:虚拟线程由JVM内部管理,而平台线程由操作系统内核管理。
  • 资源消耗:虚拟线程占用的资源更少,每个虚拟线程仅需1KB内存,而平台线程通常需要更多的资源。
  • 上下文切换:虚拟线程的上下文切换开销更小,避免了操作系统线程的上下文切换带来的系统开销。

以下是一个简单的Java 21虚拟线程示例,展示了如何创建和使用虚拟线程:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class VirtualThreadExample {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();

        for (int i = 0; i < 10; i++) {
            executor.submit(() -> {
                System.out.println("Hello from virtual thread!");
            });
        }

        executor.shutdown();
    }
}

在这个示例中,我们使用Executors.newVirtualThreadPerTaskExecutor()方法创建了一个新的虚拟线程池。每个任务都会在独立的虚拟线程中执行,从而提高了并发处理能力。

8.4、与旧版本对比

性能对比

  • Java 21虚拟线程:显著提高了并发性能,减少了资源消耗,适用于高并发场景。
  • 传统线程:创建和销毁开销较大,资源消耗较多,适用于低并发场景。
  1. 编程模型对比
  • Java 21虚拟线程:与传统的Thread API高度兼容,简化了异步编程模型,降低了开发复杂度。
  • 传统线程:需要手动管理线程的创建、销毁和同步,编程模型较为复杂。
  1. 资源消耗对比
  • Java 21虚拟线程:每个虚拟线程仅占用1KB内存,启动时间仅需1微秒。
  • 传统线程:每个线程通常需要更多的内存和资源,启动时间较长。

8.5、总结

Java 21引入的虚拟线程特性显著提升了并发性能和资源利用率,简化了异步编程模型。通过减少线程的创建和销毁开销,避免了操作系统线程的上下文切换,从而提高了系统的吞吐量和响应速度。虚拟线程与传统的Thread API高度兼容,使得现有代码可以轻松迁移到虚拟线程环境中。

Java 虚拟线程的详细解读和原理可以看:

9、JEP 445:未命名类和实例 main 方法 (预览)

这个特性主要简化了 main 方法的的声明。对于 Java 初学者来说,这个 main 方法的声明引入了太多的 Java 语法概念,不利于初学者快速上手。

没有使用该特性之前定义一个 main 方法:

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, World!");
    }
}

使用该新特性之后定义一个 main 方法:

class HelloWorld {
    void main() {
        System.out.println("Hello, World!");
    }
}

进一步精简(未命名的类允许我们不定义类名):

void main() {
    System.out.println("Hello, World!");
}

10、JEP 446:作用域值(Scoped Values,预览)

作用域值(Scoped Values) 是Java 21引入的一项预览特性,旨在提供一种更安全、更灵活的数据共享机制,特别是在多线程环境下。作用域值类似于线程本地变量(ThreadLocal),但具有明确的作用域概念,超出作用域后将被自动销毁,从而避免了内存泄漏等问题。

以下是一个简单的示例,展示了如何使用作用域值来模拟用户登录过程:

import java.util.concurrent.Callable;
import jdk.incubator.concurrent.ScopedValue;

public class ScopedValueExample {
    // 创建一个作用域值实例
    private static final ScopedValue<String> loginUser = ScopedValue.newInstance();

    public static void main(String[] args) {
        // 设置作用域值并执行操作
        ScopedValue.where(loginUser, "Alice").run(() -> {
            // 在run方法中可以随时获取作用域值
            System.out.println("Current user: " + loginUser.get());
            performAction();
        });
    }

    private static void performAction() {
        // 无需传递参数,直接获取作用域值
        System.out.println("Action performed by user: " + loginUser.get());
    }
}

与旧版本的对比分析

ThreadLocal vs. 作用域值

  • ThreadLocal:
    • 适用于单线程环境,但在多线程和虚拟线程环境中容易导致内存泄漏。
    • 需要手动管理生命周期,容易出错。
  • 作用域值:
    • 提供了更明确的作用域概念,超出作用域后自动销毁,避免内存泄漏。
    • 简化了数据共享和管理,提高了代码的可读性和健壮性。
    • 适用于多线程和虚拟线程环境,特别适合大型程序的组件间数据共享。

Java 21的作用域值特性提供了一种更安全、更灵活的数据共享机制,尤其适用于多线程环境下的数据管理。与ThreadLocal相比,作用域值具有更明确的作用域概念和自动销毁机制,简化了数据共享和管理,提高了代码的可维护性和性能。

11、JEP 448:外部函数和内存 API(第三次预览)

Java 21中引入了外部函数和内存API(Foreign Function & Memory API),这是Java与本地代码互操作的重要里程碑。该API旨在替代JNI(Java Native Interface),提供更安全、更高效的调用本地代码和处理本地内存的方式。主要改进包括:

  • 增强的布局路径:支持更复杂的内存布局。
  • 取消引用地址布局的新元素:提供更灵活的内存访问方式。
  • 集中管理Arena接口中本地段生命周期:简化内存管理。
  • 实现后备本地链接器:提高API的灵活性和可靠性。
  • 删除VaList:简化API,减少复杂性。

以下是一个简单的示例,展示了如何使用外部函数和内存API调用C语言的printf函数:

import jdk.incubator.foreign.*;
import static jdk.incubator.foreign.CLinker.*;

public class Main {
    public static void main(String[] args) throws Exception {
        MemorySegment segment = CLinker.toCString("Hello, world!");
        try (var scope = ResourceScope.newConfinedScope()) {
            var printfFn = CLinker.getInstance().lookup("printf", 
                    FunctionDescriptor.ofVoid(CLinker.C_POINTER), 
                    FunctionDescriptor.ofVoid(CLinker.C_POINTER));
            printfFn.invokeExact(scope, "%s%n", segment);
        }
    }
}

另一个示例,展示如何使用内存API分配和操作堆外内存:

import jdk.incubator.foreign.*;

public class ForeignFunctionDemo {
    public static void main(String[] args) {
        // 使用外部内存 API
        MemorySegment segment = MemorySegment.allocateNative(4);
        segment.set(ValueLayout.JAVA_INT, 0, 42);
        System.out.println("Value in foreign memory: " + segment.get(ValueLayout.JAVA_INT, 0));
        segment.close(); // 释放内存
    }
}

与旧版本的对比

  • JNI:JNI是Java与本地代码互操作的传统方式,但存在维护成本高、数据传递效率低等问题。
  • 外部函数和内存API:相比JNI,新的API提供了更安全、更高效的调用本地代码和处理本地内存的方式,减少了JNI的脆弱性和危险性。

通过这些改进,Java 21的外部函数和内存API为开发者提供了更强大的工具来提升程序的性能和效率,特别是在需要高性能计算或访问本地资源的场景中。

12、JEP 449: 弃用 Windows 32 位 x86 移植

在 Java 21 中,OpenJDK 团队决定弃用 Windows 32 位 x86 端口(x86-32),并计划在未来的版本中完全移除该端口。这一决策的主要原因包括:

  1. 技术进步:随着 64 位系统的普及,32 位系统的使用率逐渐下降,且 Windows 10 是最后一个支持 32 位操作系统的版本,预计将在 2025 年 10 月终止生命周期。
  2. 性能和安全性:64 位架构提供了更大的内存地址空间,提高了应用程序的性能和扩展性,同时引入了更多的保护机制,增强了安全性。
  3. 资源分配:放弃对 32 位系统的支持可以将更多的资源和精力集中在开发更先进的功能和优化上。

13、JEP 451: 准备禁止动态加载代理

目的:

  • 提高 Java 应用程序的安全性,防止恶意代码利用动态加载代理执行危险操作。
  • 简化安全配置,通过更新安全管理器和类加载器来控制动态加载代理的使用权限。

实现方式:

  • 修改类加载器(ClassLoader)和 Instrumentation API,添加新的方法来禁用动态加载代理。
  • 更新安全管理器(SecurityManager),提供更细粒度的控制。

默认行为:

  • 在 Java 21 及更高版本中,默认情况下将禁止动态加载代理。如果尝试在运行时动态加载代理,JVM 将发出警告。

警告机制:

  • 当代理被动态加载到正在运行的 JVM 中时,JVM 将发出警告,提醒用户为将来的版本做好准备。

修改类加载器

import java.lang.reflect.Method;

public class DynamicAgentLoader {
    public static void main(String[] args) throws Exception {
        ClassLoader classLoader = DynamicAgentLoader.class.getClassLoader();
        Method disallowDynamicAgentLoading = ClassLoader.class.getDeclaredMethod("disallowDynamicAgentLoading", boolean.class);
        disallowDynamicAgentLoading.setAccessible(true);
        disallowDynamicAgentLoading.invoke(classLoader, true);

        // 尝试加载代理
        Class<?> proxyClass = Class.forName("java.lang.reflect.Proxy");
        Object proxy = Proxy.newProxyInstance(
            Object.class.getClassLoader(),
            new Class<?>[]{java.lang Remotable.class},
            (proxy1, method, args1) -> System.out.println("Proxy method called")
        );

        // 检查是否成功加载代理
        if (proxy != null) {
            System.out.println("Proxy loaded successfully");
        } else {
            System.out.println("Proxy loading failed");
        }
    }
}
  1. 使用安全管理器
import java.lang.instrument.Instrumentation;

public class AgentLoader {
    public static void premain(String agentArgs, Instrumentation inst) {
        if (inst.isDynamicAgentLoadingAllowed()) {
            throw new SecurityException("Dynamic loading of agents is not allowed");
        }

        // 其他初始化操作
    }
}

旧版本(Java 8 至 Java 20):

  • 动态加载代理默认启用,用户可以通过命令行选项 -XX:+EnableDynamicAgentLoading-XX:-DisableDynamicAgentLoading 来启用或禁用。
  • 没有默认禁用动态加载代理的机制,用户需要手动管理代理的加载。

Java 21 及更高版本:

  • 默认禁用动态加载代理,提高了应用程序的安全性。
  • 通过修改类加载器和安全管理器,提供了更细粒度的控制。
  • 警告机制帮助用户提前准备,避免未来版本中的不兼容问题。

Java 21 的“准备禁止动态加载代理”特性旨在提高应用程序的安全性和简化安全配置。通过默认禁用动态加载代理并提供警告机制,用户可以更好地管理和控制代理的使用,从而提高应用程序的整体安全性。在使用该特性之前,建议仔细评估现有代码的依赖关系,确保不会影响正常运行。

14、JEP 452: 密钥封装机制 API(KEM)

KEM API的主要接口和类位于javax.crypto包下,包括KeyEncapsulationMechanismEncapsulatedKeyDecapsulatorSpiEncapsulatorSpi等。这些接口和类提供了生成密钥对、封装和解封装密钥的方法。算法支持:RSA、EC-Kyber、DH 等。

// 接收方生成密钥对
KeyPairGenerator kpg = KeyPairGenerator.getInstance("Kyber");
KeyPair kp = kpg.generateKeyPair();
// 发送方封装密钥
KEM kem = KEM.getInstance("Kyber");
KEM.Encapsulator enc = kem.newEncapsulator(kp.getPublic());
KEM.Encapsulated e = enc.encapsulate();
byte[] ciphertext = e.encapsulation(); // 发送给接收方
byte[] secretKey = e.secret();
// 接收方解封装
KEM.Decapsulator dec = kem.newDecapsulator(kp.getPrivate());
byte[] secretKey2 = dec.decapsulate(ciphertext); // 应与 secretKey 相同

与旧版本的对比

安全性提升

  • 旧版本:传统的密钥交换方法(如Diffie-Hellman)需要填充,这增加了安全风险。
  • Java 21:KEM API通过公钥加密技术,避免了填充的需求,提高了安全性。
  1. 简化密钥管理
  • 旧版本:密钥管理复杂,容易出现密钥泄露和中间人攻击。
  • Java 21:KEM API简化了密钥管理过程,提供了生成、封装和解封装密钥的方法,降低了操作复杂性。
  1. 支持更多算法
  • 旧版本:支持的加密算法有限。
  • Java 21:KEM API支持多种加密算法,包括RSA-KEM、ECIES和NIST后量子密码学标准化过程的候选算法。
  1. 跨平台兼容性
  • 旧版本:不同平台之间的密钥交换可能存在兼容性问题。
  • Java 21:KEM API设计为跨平台和跨语言的互操作性,确保Java应用能够与使用相同或兼容KEM标准的其他系统顺畅协作。

Java 21的KEM API为开发者提供了一个强大且安全的工具,用于管理和交换加密密钥。通过使用公钥加密技术,KEM API不仅提高了安全性,还简化了密钥管理过程,并支持多种加密算法和协议。这些改进使得Java 21在现代应用开发中更加安全和高效。

15、JEP 453: 结构化并发(预览)

结构化并发是Java 21引入的一项新特性,旨在简化并发编程,提高代码的可维护性和可靠性。它通过将相关任务视为单个工作单元,简化了错误处理和取消操作,增强了并发代码的可观察性。结构化并发的核心是StructuredTaskScope类,该类允许将任务构建为一系列并发子任务,并将它们作为一个单元进行协调。子任务在各自的线程中执行,然后被父任务聚合和处理。StructuredTaskScope提供了短路错误处理、取消传播、逻辑清晰度和可观测性等特性。

以下是一个使用StructuredTaskScope的示例代码:

try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
    Future<Integer> future1 = scope.fork(() -> heavyComputation1());
    Future<Integer> future2 = scope.fork(() -> heavyComputation2());
    scope.join(); // 等待所有子任务完成
    scope.throwIfFailed(); // 如果有子任务失败,抛出异常

    Integer result1 = futureresultNow();
    Integer result2 = future2.resultNow();
    System.out.println("Results: " + result1 + ", " + result2);
}

与旧版本的对比分析

在Java 21之前,开发者通常使用ExecutorServiceFuture来管理并发任务。这种方式虽然灵活,但也存在一些问题,例如任务间关系不明确,错误处理复杂,容易导致资源浪费和线程泄漏。

结构化并发与ExecutorServiceFuture的主要区别在于:

  1. 任务关系明确:结构化并发通过StructuredTaskScope将任务视为单个工作单元,确保子任务的生命周期嵌套在其父任务的生命周期之内。
  2. 错误处理简化:结构化并发提供了更简单的错误处理机制,当一个子任务失败时,其他子任务可以自动取消。
  3. 取消传播:如果父任务被取消,所有子任务也会被取消,避免了资源浪费。
  4. 可观察性增强:结构化并发通过反映代码结构的任务层次,简化了单线程代码中的任务管理,提高了代码的可读性和可维护性。

总之,Java 21中的结构化并发为并发编程提供了更高级别的控制和优化,有助于提高代码的可维护性和性能。

二、