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.BuilderAPI 微调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 没有正式的新特性,因此:
- 生产环境建议直接跳过 JDK 20------从 JDK 17 或 JDK 19 升级到 JDK 21
- 若必须在 JDK 20 上使用预览特性,需
--enable-preview,且升级到 JDK 21 时必须重新编译 - Scoped Values 在 JDK 21 中从
jdk.incubator.concurrent移到java.lang(包名变化) MemorySession在 JDK 21 中改回Arena的形态调整(API 变化)- 结构化并发 API 在 JDK 21 中有较大调整 (
Subtask与Future的关系)
五、参考资源
- 官方发布页:https://openjdk.org/projects/jdk/20/
- JEP 436(虚拟线程):https://openjdk.org/jeps/436
- JEP 429(Scoped Values):https://openjdk.org/jeps/429
- JEP 432(Record Patterns):https://openjdk.org/jeps/432
- JEP 433(Switch 模式匹配):https://openjdk.org/jeps/433
- JEP 434(FFM API):https://openjdk.org/jeps/434