JDK 20 新特性详解

JDK 20 新特性详解

发布日期 :2023 年 3 月 21 日

规范版本 :Java SE 20(JSR 395)

是否 LTS :否

主题:虚拟线程第二次预览、Scoped Values 首次孵化、纯粹是 JDK 21 的「预热版」


一、版本总览

JDK 20 共集成 7 个 JEP ,全部是预览或孵化特性,没有任何正式特性。它是一个典型的「过渡版本」------所有重要特性都在 JDK 21 转正。

JEP 特性 从 到
429 Scoped Values 新 孵化
432 Record Patterns 19 预览 第二次预览
433 Switch 模式匹配 19 第三次预览 第四次预览
434 外部函数与内存 API 19 预览 第二次预览
436 虚拟线程 19 预览 第二次预览
437 结构化并发 19 孵化 第二次孵化
438 Vector API 19 第四次孵化 第五次孵化

结论:JDK 20 不建议在生产使用(没有正式新特性),直接从 JDK 17 或 JDK 19 跳到 JDK 21。


二、各特性详解

1. JEP 436:虚拟线程(第二次预览)

相比 JDK 19(JEP 425)的主要变更:

① Thread API 稳定化

  • Thread.ofVirtual() / Thread.ofPlatform() 的 Thread.Builder API 微调
  • Thread.startVirtualThread(Runnable) 保持

② 调度器可配置

java 复制代码
// JDK 20 起:可以指定自定义调度器(通过系统属性)
// -Djdk.virtualThreadScheduler.parallelism=N   并行度
// -Djdk.virtualThreadScheduler.maxPoolSize=N   最大载体线程数
bash 复制代码
java -Djdk.virtualThreadScheduler.parallelism=4 MyApp
java -Djdk.virtualThreadScheduler.maxPoolSize=256 MyApp

③ 改进的固定(Pinning)诊断

bash 复制代码
# 打印被固定的虚拟线程
java -Djdk.tracePinnedThreads=full MyApp
java -Djdk.tracePinnedThreads=short MyApp

④ 明确不支持的操作

操作 行为
Thread.setPriority() 无效果(虚拟线程无优先级)
Thread.getPriority() 始终返回 Thread.NORM_PRIORITY
Thread.setDaemon(false) 抛 UnsupportedOperationException
ThreadGroup 操作 有限支持

⑤ 虚拟线程与线程局部变量的建议

  • 避免在虚拟线程中使用 ThreadLocal 缓存大对象
  • 推荐使用 Scoped Values(JEP 429,JDK 20 首次孵化)

虚拟线程完整示例:

java 复制代码
// 典型的服务器场景
public class VirtualThreadServer {
    public static void main(String[] args) throws IOException {
        try (var server = ServerSocketChannel.open()) {
            server.bind(new InetSocketAddress(8080));

            // 用虚拟线程池处理每个连接
            try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
                while (true) {
                    SocketChannel client = server.accept();
                    executor.submit(() -> handle(client));
                }
            }
        }
    }

    static void handle(SocketChannel client) {
        try (client) {
            ByteBuffer buf = ByteBuffer.allocate(8192);
            // 阻塞式读取 ------ 自动卸载,不占用 OS 线程
            client.read(buf);
            buf.flip();

            // 阻塞式调用下游服务
            var response = downstreamClient.send(...);

            client.write(ByteBuffer.wrap(response));
        } catch (IOException e) {
            // ...
        }
    }
}

虚拟线程的适用场景:

场景 推荐
I/O 密集型(HTTP 调用、DB 查询、文件操作) ✅ 虚拟线程
CPU 密集型计算 ❌ 平台线程(或并行流/ForkJoinPool)
需要大量并发但任务短小 ✅ 虚拟线程

2. JEP 429:Scoped Values(孵化)------首次登场

这是 JDK 25(JEP 506)转正特性的起点。

要解决的问题 :ThreadLocal 的三个缺陷

缺陷 说明
无约束的可变性 任何能调用 get() 的代码都能调用 set(),数据流混乱
无界的生命周期 一旦 set(),值保留到线程结束或显式 remove();线程池中容易泄漏
昂贵的继承 InheritableThreadLocal 在创建子线程时必须复制所有值,内存开销大

Scoped Values 的设计目标:

  • 不可变:一旦绑定,只能读取
  • 有界生命周期 :只在 run() 调用期间有效
  • 轻量继承:子线程可高效共享(写时复制语义)

API 结构(JDK 20 孵化版,位于 jdk.incubator.concurrent):

java 复制代码
import jdk.incubator.concurrent.ScopedValue;

// 1. 声明(通常是 static final)
public static final ScopedValue<UserInfo> CURRENT_USER = ScopedValue.newInstance();

// 2. 绑定并使用
ScopedValue.where(CURRENT_USER, userInfo).run(() -> {
    // 在这个作用域内,CURRENT_USER.get() 返回 userInfo
    handleRequest();
});

// 3. 在任意深度的方法中读取
void handleRequest() {
    UserInfo user = CURRENT_USER.get();   // 无需传递参数
    // ...
}

完整的框架示例:

java 复制代码
public class Framework {
    private static final ScopedValue<FrameworkContext> CONTEXT =
            ScopedValue.newInstance();

    void serve(Request request, Response response) {
        var context = createContext(request);

        ScopedValue.where(CONTEXT, context)
                   .run(() -> Application.handle(request, response));

        // 离开 run() 后,CONTEXT 自动失效
    }

    public PersistedObject readKey(String key) {
        var context = CONTEXT.get();     // 无需参数传递
        var db = getDBConnection(context);
        return db.readKey(key);
    }
}

public class Application {
    void handle(Request request, Response response) {
        // 用户代码无需知道 FrameworkContext 的存在
        var userInfo = readUserInfo();
        response.write(userInfo);
    }

    private UserInfo readUserInfo() {
        return (UserInfo) framework.readKey("userInfo");
    }
}

关键 API:

java 复制代码
public final class ScopedValue<T> {
    // 创建
    public static <T> ScopedValue<T> newInstance();

    // 读取
    public T get();                              // 未绑定时抛 NoSuchElementException
    public boolean isBound();
    public T orElse(T other);
    public T orElseThrow(Supplier<? extends X> exceptionSupplier);

    // 绑定
    public static <T> Carrier where(ScopedValue<T> key, T value);

    public static final class Carrier {
        public void run(Runnable op);
        public <R> R call(Callable<? extends R> op);
        public Carrier where(ScopedValue<?> key, Object value);   // 链式嵌套
    }
}

嵌套绑定:

java 复制代码
ScopedValue.where(A, 1)
           .where(B, 2)
           .where(C, 3)
           .run(() -> {
               System.out.println(A.get() + B.get() + C.get());   // 6
           });

子线程继承:

java 复制代码
ScopedValue.where(CURRENT_USER, alice).run(() -> {
    // 子线程可以看到绑定(通过结构化并发 fork 或直接创建)
    try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
        var f = scope.fork(() -> {
            // 这里可以读到 CURRENT_USER.get() == alice
            return CURRENT_USER.get();
        });
        // ...
    }
});

3. JEP 432:Record Patterns(第二次预览)

相比 JDK 19(JEP 405)的变更:

① 支持泛型类型参数的显式推断

java 复制代码
record Box<T>(T t) { }

// 现在可以显式指定类型参数
if (obj instanceof Box<String>(String s)) {
    System.out.println(s.toUpperCase());
}

② 支持嵌套的 record pattern 中的泛型

java 复制代码
record Pair<A, B>(A a, B b) { }
record Wrapper<T>(T inner) { }

// 嵌套解构 + 泛型
if (obj instanceof Wrapper<Pair<String, Integer>>(
        Pair<String, Integer>(String s, Integer i))) {
    System.out.println(s + " -> " + i);
}

③ 数组模式被移除

JDK 19 中曾提议的数组模式(case int[] {1, 2, var x} 之类)在 JDK 20 中被移除,理由是需要更充分的设计讨论。

④ var 在 record pattern 中的使用

java 复制代码
record Point(int x, int y) { }

if (obj instanceof Point(var x, var y)) {
    // x、y 的类型由 Point 的组件类型推断:int
    System.out.println(x + y);
}

4. JEP 433:Switch 模式匹配(第四次预览)

相比 JDK 19(JEP 427)的变更(主要是细化与 bug 修复):

① 更精确的支配性检查

java 复制代码
// 编译错误:case 顺序错误
switch (obj) {
    case CharSequence cs -> ...      // 支配 String
    case String s -> ...             // ❌ String 是 CharSequence 的子类
}

② case null, default 的支持保持

java 复制代码
switch (obj) {
    case null, default -> System.out.println("null or other");
    case String s      -> System.out.println(s);
}

③ 守卫(when)的完善

java 复制代码
switch (obj) {
    case String s when s.isEmpty()     -> "empty";
    case String s when s.length() > 100 -> "long";
    case String s                       -> "short";
    default                             -> "other";
}

④ 模式的穷尽性与密封类

java 复制代码
sealed interface Shape permits Circle, Square, Rectangle { }
record Circle(double r) implements Shape { }
record Square(double s) implements Shape { }
record Rectangle(double w, double h) implements Shape { }

// 编译器可验证穷尽性,无需 default
double area(Shape shape) {
    return switch (shape) {
        case Circle(double r)              -> Math.PI * r * r;
        case Square(double s)              -> s * s;
        case Rectangle(double w, double h) -> w * h;
    };
}

5. JEP 434:外部函数与内存 API(第二次预览)

相比 JDK 19(JEP 424)的重大变更:

① MemorySession → Arena

java 复制代码
// JDK 19
try (MemorySession session = MemorySession.openConfined()) {
    MemorySegment seg = MemorySegment.allocateNative(100, session);
}

// JDK 20
try (Arena arena = Arena.openConfined()) {
    MemorySegment seg = arena.allocate(100);
}

② 分离的 ValueLayout 与 MemoryLayout

③ Linker API 重构

java 复制代码
import java.lang.foreign.*;

Linker linker = Linker.nativeLinker();
SymbolLookup stdlib = linker.defaultLookup();

// 调用 C 函数
MethodHandle strlen = linker.downcallHandle(
        stdlib.find("strlen").orElseThrow(),
        FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS));

try (Arena arena = Arena.openConfined()) {
    MemorySegment cStr = arena.allocateUtf8String("Hello");
    long len = (long) strlen.invoke(cStr);
    System.out.println(len);   // 5
}

④ 结构体布局与 VarHandle

java 复制代码
StructLayout pointLayout = MemoryLayout.structLayout(
        ValueLayout.JAVA_INT.withName("x"),
        ValueLayout.JAVA_INT.withName("y"));

VarHandle xHandle = pointLayout.varHandle(
        MemoryLayout.PathElement.groupElement("x"));
VarHandle yHandle = pointLayout.varHandle(
        MemoryLayout.PathElement.groupElement("y"));

try (Arena arena = Arena.openConfined()) {
    MemorySegment point = arena.allocate(pointLayout);
    xHandle.set(point, 10);
    yHandle.set(point, 20);
    System.out.println(xHandle.get(point) + ", " + yHandle.get(point));   // 10, 20
}

6. JEP 437:结构化并发(第二次孵化)

相比 JDK 19(JEP 428)的变更:

① StructuredTaskScope 的策略类保持

java 复制代码
// ShutdownOnFailure
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
    Subtask<String> user  = scope.fork(() -> findUser());
    Subtask<Integer> order = scope.fork(() -> fetchOrder());

    scope.join();
    scope.throwIfFailed();

    return new Response(user.get(), order.get());
}

// ShutdownOnSuccess
try (var scope = new StructuredTaskScope.ShutdownOnSuccess<String>()) {
    scope.fork(() -> fetchFromA());
    scope.fork(() -> fetchFromB());
    scope.join();
    return scope.result();
}

② 与 Scoped Values 的协同

java 复制代码
ScopedValue.where(CONTEXT, ctx).run(() -> {
    try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
        scope.fork(() -> {
            // 子任务中可读到 CONTEXT
            var c = CONTEXT.get();
            return doWork(c);
        });
        scope.join();
    }
});

③ Subtask 接口

fork() 返回 Subtask<T>(而不是 Future<T>),以区别于 java.util.concurrent.Future:

java 复制代码
public interface Subtask<T> extends Supplier<T> {
    T get();
    T resultNow();
    Throwable exceptionNow();
    Future.State state();
}

(JDK 21 的 JEP 453 中,Subtask 改为真正的 java.util.concurrent.Future 的实现。)

7. JEP 438:Vector API(第五次孵化)

相比 JDK 19(JEP 426):

  • 增强对 AArch64 的支持
  • VectorMask 的更多操作
  • 改进 Float16 支持
  • API 微调与文档完善

三、JDK 20 特性速查表

JEP 特性 状态 后续
429 Scoped Values 首次孵化 JDK 25 转正(JEP 506)
432 Record Patterns 第二次预览 JDK 21 转正(JEP 440)
433 Switch 模式匹配 第四次预览 JDK 21 转正(JEP 441)
434 外部函数与内存 API 第二次预览 JDK 22 转正(JEP 454)
436 虚拟线程 第二次预览 JDK 21 转正(JEP 444)
437 结构化并发 第二次孵化 至今仍在预览(JDK 27 JEP 533)
438 Vector API 第五次孵化 至今仍在孵化(JDK 27 JEP 537)

四、升级注意事项

JDK 20 没有正式的新特性,因此:

  1. 生产环境建议直接跳过 JDK 20------从 JDK 17 或 JDK 19 升级到 JDK 21
  2. 若必须在 JDK 20 上使用预览特性,需 --enable-preview,且升级到 JDK 21 时必须重新编译
  3. Scoped Values 在 JDK 21 中从 jdk.incubator.concurrent 移到 java.lang(包名变化)
  4. MemorySession 在 JDK 21 中改回 Arena 的形态调整(API 变化)
  5. 结构化并发 API 在 JDK 21 中有较大调整 (Subtask 与 Future 的关系)

五、参考资源


← JDK 19 | 返回索引 | JDK 21 →

相关推荐
木井巳1 小时前
【JavaEE】SpringBoot 统一功能处理: 拦截器、统一返回与全局异常
java·spring boot·后端·spring·java-ee
SL-staff1 小时前
JVS-Rules金融风控实践:四大原生能力的技术实现与可落地配置指南
java·决策树·规则引擎·函数计算·金融风控·jvs-rules·数据源抽象
Thomas.Sir2 小时前
第6课:SpringBoot4.x 核心注解全解:Component、Bean、Scope、Lazy等IOC相关注解
java·spring boot
paopaokaka_luck2 小时前
美食推荐系统(协同过滤算法推荐、AI助手、HanLP中文分词、 ECharts可视化分析、菜谱学习与话题交流)
java·spring boot·mysql·tomcat·echarts
开源推荐官2 小时前
2026 年开源商城源码选型参考:技术栈、功能与适用场景梳理
java·架构·开源
sunburn-2 小时前
数据库操作完整教程:从基础到实战
java·数据库·mysql
文丑颜不良啊2 小时前
LeetCode576~600题目解析
java·leetcode
Linux运维技术栈2 小时前
CVE-2026-91403 高危漏洞预警:Java JSSE 信任链被攻破 全场景修复方案与Linux系统级彻底清理实操
java·linux·安全·jdk·信任链
秋饼2 小时前
Spring AI + MCP 工具调用可靠性实战:参数校验、权限防线与人工确认生产级落地
java·ai·技术分享·后端开发