Java 21 是鱼皮做新项目时使用的首选 LTS 版本。这个版本发布了很多重要特性,其中最重要的是 Virtual Threads 虚拟线程
正式特性
【必备】Virtual Threads 虚拟线程
这是 Java 并发编程的革命性突破,也是很多 Java 开发者选择 21 的理由。
什么是虚拟线程呢?
想象一下,你是一家餐厅的老板。传统的线程就像是餐厅的服务员,假设每个服务员同时只能服务一桌客人。如果有 1000 桌客人,你就需要 1000 个服务员,但这显然不现实。餐厅地方不够,也负担不起那么多员工的工钱。
在传统的 Java 线程模型中也是如此。如果每个线程都对应操作系统的一个真实线程,创建成本很高、内存占用也大。当需要处理大量并发请求时,系统可能很快就会被拖垮。
举个例子,假设开 1000 个线程同时处理网络请求:
public void handleRequests() {
for (int i = 0; i < 1000; i++) {
new Thread(() -> {
// 发送网络请求,需要等待响应
String result = httpClient.get("https://codefather.cn");
System.out.println("收到响应: " + result);
}).start();
}
}创建 1000 个线程会消耗大量系统资源(因为对应 1000 个操作系统线程),而且大部分时间线程都在等待网络响应,很浪费。
而虚拟线程就像是给餐厅引入了一个智能调度系统。服务员不再需要傻傻地等在客人桌边等菜上桌,而是可以在等待的时候去服务其他客人。当某桌的菜准备好了,系统会自动安排一个空闲的服务员去处理。
我们可以开一个虚拟线程执行器执行同样的一批任务,这里我用的执行器会为每个任务生成一个虚拟线程来处理:
public void handleRequestsWithVirtualThreads() {
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < 1000; i++) {
executor.submit(() -> {
// 同样的网络请求代码
String result = httpClient.get("https://codefather.cn");
System.out.println("收到响应: " + result);
});
}
}
}同样是 1000 个,但是 1000 个虚拟线程只需要很少的系统资源(比如映射到 8 个操作系统线程上);而且当虚拟线程等待网络响应时,会让出底层的操作系统线程,操作系统线程就会自动切换去执行其他虚拟线程和任务。

总结一下 Virtual Threads 的核心优势。首先是 超级轻量。一个传统线程可能需要几 MB 的内存,而一个虚拟线程只需要几 KB。你可以轻松创建百万级别的虚拟线程而不用担心系统资源。
其次是 编程简单。你不需要学习复杂的异步编程模式,跟创建一个普通线程的代码类似,一行代码就能提交异步任务。当遇到阻塞的 I/O 操作时,虚拟线程会自动让出底层的操作系统线程。
// 直接创建虚拟线程
public void handleSingleUser(Long userId) {
Thread.ofVirtual().start(() -> {
// 要异步执行的任务
User user = userService.findById(userId);
processUser(user);
});
}相关面试题:什么是协程?Java 支持协程吗?
虚拟线程的实际应用
public class VirtualThreadWebServer {
public static void main(String[] args) throws IOException {
// 创建虚拟线程执行器
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
ServerSocket serverSocket = new ServerSocket(8080);
System.out.println("服务器启动在端口 8080");
while (true) {
Socket clientSocket = serverSocket.accept();
// 为每个客户端连接创建虚拟线程
executor.submit(() -> handleClient(clientSocket));
}
}
}
private static void handleClient(Socket clientSocket) {
try (BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true)) {
String inputLine;
while ((inputLine = in.readLine()) != null) {
// 模拟处理请求(可能涉及数据库查询等 I/O 操作)
Thread.sleep(100); // 虚拟线程会自动让出
out.println("Echo: " + inputLine);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}虚拟线程与传统线程池的对比
public class ThreadComparisonExample {
public static void main(String[] args) throws InterruptedException {
int taskCount = 100000;
// 传统线程池方式
long start = 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(10);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
latch.countDown();
}
});
}
latch.await();
}
System.out.println("传统线程池耗时: " + (System.currentTimeMillis() - start) + "ms");
// 虚拟线程方式
start = System.currentTimeMillis();
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
CountDownLatch latch = new CountDownLatch(taskCount);
for (int i = 0; i < taskCount; i++) {
executor.submit(() -> {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
latch.countDown();
}
});
}
latch.await();
}
System.out.println("虚拟线程耗时: " + (System.currentTimeMillis() - start) + "ms");
}
}【必备】Switch 模式匹配
Java 14 版本推出了 Switch 表达式,能够一行处理多个条件;Java 21 版本进一步优化了 Switch 的能力,新增了模式匹配特性,能够更轻松地根据对象的类型做不同的处理。
没有 Switch 模式匹配时,我们需要利用 instanceof 匹配类型:
public String processMessage(Object message) {
if (message instanceof String) {
String textMessage = (String) message;
return "文本消息:" + textMessage;
} else if (message instanceof Integer) {
Integer numberMessage = (Integer) message;
return "数字消息:" + numberMessage;
} else if (message instanceof List) {
List<?> listMessage = (List<?>) message;
return "列表消息,包含 " + listMessage.size() + " 个元素";
} else {
return "未知消息类型";
}
}有了模式匹配,这段代码可以变得很优雅,直接在匹配对象类型的同时声明了变量(跟 instanceof 模式匹配有点像):
public String processMessage(Object message) {
return switch (message) {
case String text -> "文本消息:" + text;
case Integer number -> "数字消息:" + number;
case List<?> list -> "列表消息,包含 " + list.size() + " 个元素";
case null -> "空消息";
default -> "未知消息类型";
};
}此外,模式匹配还支持 条件判断,让处理逻辑更加精细,相当于在 case ... when ... 中写 if 条件表达式(感觉有点像 SQL 的语法)。QRpbURJSGNCpit+OOVbcJpS1SrMo+q9/tiuOML5mzhA=
// 根据字符串长度采用不同处理策略
public String processText(String text) {
return switch (text) {
case String s when s.length() < 10 -> "短文本:" + s;
case String s when s.length() < 100 -> "中等文本:" + s.substring(0, 5);
case String s -> "长文本:" + s.substring(0, 10);
};
}模式匹配的高级用法
public class AdvancedPatternMatching {
// 定义一些数据类型
public sealed interface Expression permits Constant, BinaryOp {}
public record Constant(int value) implements Expression {}
public record BinaryOp(String operator, Expression left, Expression right) implements Expression {}
// 使用模式匹配计算表达式
public int evaluate(Expression expr) {
return switch (expr) {
case Constant(var value) -> value;
case BinaryOp("+", var left, var right) -> evaluate(left) + evaluate(right);
case BinaryOp("-", var left, var right) -> evaluate(left) - evaluate(right);
case BinaryOp("*", var left, var right) -> evaluate(left) * evaluate(right);
case BinaryOp("/", var left, var right) -> evaluate(left) / evaluate(right);
case BinaryOp(var op, var left, var right) ->
throw new UnsupportedOperationException("不支持的操作符: " + op);
};
}
// 处理复杂的数据结构
public String processData(Object data) {
return switch (data) {
case null -> "空数据";
case String s when s.isEmpty() -> "空字符串";
case String s when s.length() == 1 -> "单字符: " + s;
case String s -> "字符串: " + s;
case Integer i when i == 0 -> "零";
case Integer i when i > 0 -> "正整数: " + i;
case Integer i -> "负整数: " + i;
case List<?> list when list.isEmpty() -> "空列表";
case List<?> list when list.size() == 1 -> "单元素列表: " + list.get(0);
case List<?> list -> "列表,大小: " + list.size();
default -> "未知类型";
};
}
}【实用】Record 模式
Record 模式让数据的解构变得更简单直观,可以一次性取出 record 中所有需要的信息。
举个例子,先定义一些简单的 Record:
public record Person(String name, int age) {}
public record Address(String city, String street) {}
public record Employee(Person person, Address address, double salary) {}使用 Record 模式可以直接解构这些数据,不用一层一层取了:
public String analyzeEmployee(Employee emp) {
return switch (emp) {
// 一次性提取所有需要的信息
case Employee(Person(var name, var age), Address(var city, var street), var salary)
when salary > 50000 ->
String.format("%s(%d岁)是高薪员工,住在%s%s,月薪%.0f",
name, age, city, street, salary);
case Employee(Person(var name, var age), var address, var salary) ->
String.format("%s(%d岁)月薪%.0f,住在%s",
name, age, salary, address.city());
};
}这种写法适合追求极致简洁代码的程序员,可以在一行代码中同时完成 类型检查、数据提取 和 条件判断。
Record 模式的实际应用
public class RecordPatternExample {
// 定义 HTTP 响应的数据结构
public sealed interface HttpResponse permits Success, Error, Redirect {}
public record Success(int code, String body, Map<String, String> headers) implements HttpResponse {}
public record Error(int code, String message) implements HttpResponse {}
public record Redirect(int code, String location) implements HttpResponse {}
// 处理 HTTP 响应
public void handleResponse(HttpResponse response) {
switch (response) {
case Success(var code, var body, var headers) when code == 200 -> {
System.out.println("成功响应: " + body);
if (headers.containsKey("Content-Type")) {
System.out.println("内容类型: " + headers.get("Content-Type"));
}
}
case Success(var code, var body, var headers) -> {
System.out.println("成功响应 " + code + ": " + body);
}
case Error(var code, var message) when code >= 500 -> {
System.err.println("服务器错误 " + code + ": " + message);
}
case Error(var code, var message) -> {
System.err.println("客户端错误 " + code + ": " + message);
}
case Redirect(var code, var location) -> {
System.out.println("重定向 " + code + " -> " + location);
}
}
}
// 解析嵌套的数据结构
public void processOrder(Object order) {
record Item(String name, double price, int quantity) {}
record Customer(String name, String email) {}
record Order(Customer customer, List<Item> items, double total) {}
switch (order) {
case Order(Customer(var customerName, var email), var items, var total)
when total > 1000 -> {
System.out.println("大订单:客户 " + customerName + " (" + email + ")");
System.out.println("订单总额:" + total);
items.forEach(item -> System.out.println("- " + item.name() + " x" + item.quantity()));
}
case Order(Customer(var customerName, var email), var items, var total) -> {
System.out.println("普通订单:客户 " + customerName);
System.out.println("商品数量:" + items.size() + ",总额:" + total);
}
default -> System.out.println("无效订单");
}
}
}【了解】有序集合
Java 21 的有序集合为我们提供了更直观的方式来操作集合的头尾元素,说白了就是补了几个方法:
List<String> tasks = new ArrayList<>();
tasks.addFirst("鱼皮的任务"); // 添加到开头
tasks.addLast("小阿巴的任务"); // 添加到结尾
String firstStr = tasks.getFirst(); // 获取第一个
String lastStr = tasks.getLast(); // 获取最后一个
String removedFirst = tasks.removeFirst(); // 删除并返回第一个
String removedLast = tasks.removeLast(); // 删除并返回最后一个
List<String> reversed = tasks.reversed(); // 反转列表除了 List 之外,SequencedMap 接口(比如 LinkedHashMap)和 SequencedSet 接口(比如 LinkedHashSet)也新增了类似的方法。本质上都是实现了有序集合接口:

有序集合的使用示例
public class SequencedCollectionExample {
public static void main(String[] args) {
// List 的有序操作
List<String> list = new ArrayList<>();
list.addFirst("开始");
list.addLast("结束");
list.addFirst("真正的开始");
System.out.println("列表: " + list); // [真正的开始, 开始, 结束]
System.out.println("第一个: " + list.getFirst());
System.out.println("最后一个: " + list.getLast());
// Set 的有序操作
SequencedSet<Integer> set = new LinkedHashSet<>();
set.addFirst(1);
set.addLast(3);
set.addFirst(0);
set.addLast(4);
System.out.println("有序集合: " + set); // [0, 1, 3, 4]
System.out.println("反转后: " + set.reversed()); // [4, 3, 1, 0]
// Map 的有序操作
SequencedMap<String, String> map = new LinkedHashMap<>();
map.putFirst("first", "第一个");
map.putLast("last", "最后一个");
map.putFirst("zero", "第零个");
System.out.println("有序映射: " + map);
System.out.println("第一个键值对: " + map.firstEntry());
System.out.println("最后一个键值对: " + map.lastEntry());
}
}【了解】分代 ZGC
Java 21 中的分代 ZGC 可以说是垃圾收集器领域的一个重大突破。ZGC 从 Java 11 开始就以其超低延迟而闻名,但是它并没有采用分代的设计思路。
在这之前,ZGC 对所有对象一视同仁,无论是刚创建的新对象还是存活了很久的老对象,都使用同样的收集策略。这虽然保证了一致的低延迟,但在内存分配密集的应用中,效率并不是最优的
分代 ZGC 的核心思想是基于一个现象 —— 大部分对象都是 "朝生夕死" 的。它将堆内存划分为年轻代和老年代两个区域,年轻代的垃圾收集可以更加频繁和高效,因为大部分年轻对象很快就会死亡,收集器可以快速清理掉这些垃圾;而老年代的收集频率相对较低,减少了对长期存活对象的不必要扫描。

分代 ZGC 的使用
# 启用分代 ZGC
java -XX:+UseZGC -XX:+UnlockExperimentalVMOptions -XX:+UseGenerationalZGC MyApp
# 监控 GC 性能
java -XX:+UseZGC -XX:+UnlockExperimentalVMOptions -XX:+UseGenerationalZGC \
-Xlog:gc:gc.log MyApp
# 调整年轻代大小(可选)
java -XX:+UseZGC -XX:+UnlockExperimentalVMOptions -XX:+UseGenerationalZGC \
-XX:NewRatio=2 MyApp分代 ZGC 的优势:
更低的分配开销:年轻代分配更高效
更少的 GC 工作:大部分对象在年轻代就被回收
更好的缓存局部性:年轻对象聚集在一起
保持低延迟:继承了 ZGC 的低延迟特性
【了解】其他正式特性
弃用 Windows 32 位 x86 端口
Java 21 将 Windows 32 位支持标记为弃用:
# 32 位 Windows 系统将不再被官方支持
# 建议迁移到 64 位系统准备禁止代理的动态加载
Java 21 为禁止运行时动态加载 Java 代理做准备:
# 未来版本将不允许运行时加载代理
# java -javaagent:agent.jar MyApp # 启动时加载仍然支持
# 如果需要运行时加载(不推荐)
java -XX:+EnableDynamicAgentLoading MyApp密钥封装机制 API
Java 21 引入了密钥封装机制(KEM)API:
import javax.crypto.KEM;
import javax.crypto.KeyGenerator;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
public class KEMExample {
public static void main(String[] args) throws Exception {
// 生成密钥对
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(2048);
KeyPair keyPair = kpg.generateKeyPair();
// 创建 KEM 实例
KEM kem = KEM.getInstance("RSA-KEM");
// 发送方:封装密钥
KEM.Encapsulator encapsulator = kem.newEncapsulator(keyPair.getPublic());
KEM.Encapsulated encapsulated = encapsulator.encapsulate();
byte[] encapsulatedKey = encapsulated.encapsulation();
byte[] sharedSecret = encapsulated.key();
// 接收方:解封装密钥
KEM.Decapsulator decapsulator = kem.newDecapsulator(keyPair.getPrivate());
byte[] recoveredSecret = decapsulator.decapsulate(encapsulatedKey);
// 验证密钥一致性
System.out.println("密钥封装成功: " +
java.util.Arrays.equals(sharedSecret, recoveredSecret));
}
}预览特性
【了解】字符串模板(预览)
Java 21 引入了字符串模板作为预览特性:
// 需要启用预览功能
// javac --enable-preview --release 21 StringTemplateExample.java
// java --enable-preview StringTemplateExample
public class StringTemplateExample {
public static void main(String[] args) {
String name = "张三";
int age = 25;
double salary = 15000.50;
// 字符串模板
String message = STR."员工 \{name} 年龄 \{age},月薪 \{salary}";
System.out.println(message);
// 格式化模板
String formatted = FMT."员工 %-10s\{name} 年龄 %3d\{age},月薪 %8.2f\{salary}";
System.out.println(formatted);
// 原始模板
StringTemplate st = RAW."员工 \{name} 年龄 \{age},月薪 \{salary}";
System.out.println("模板: " + st.template());
System.out.println("值: " + st.values());
}
}【了解】未命名模式和变量(预览)
Java 21 引入了未命名模式和变量:
// 未命名变量
for (int i = 0, _ = sideEffect(); i < 10; i++) {
// _ 表示不关心的变量
}
// 在 switch 中使用未命名模式
switch (obj) {
case Point(var x, _) -> System.out.println("X坐标: " + x);
case String _ -> System.out.println("某个字符串");
default -> System.out.println("其他");
}
// 在 catch 中使用
try {
riskyOperation();
} catch (IOException _) {
// 不关心异常对象
System.out.println("IO 异常发生");
}【了解】未命名类和实例主方法(预览)
Java 21 简化了简单程序的编写:
// 传统写法
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello World");
}
}
// Java 21 简化写法(预览)
void main() {
System.out.println("Hello World");
}
// 或者
public static void main() {
System.out.println("Hello World");
}孵化器特性
【了解】向量 API(第六次孵化器)
Java 21 继续完善向量 API:
import jdk.incubator.vector.*;
public class VectorExample {
private static final VectorSpecies<Float> SPECIES = FloatVector.SPECIES_PREFERRED;
// 向量化的矩阵乘法
public static void matrixMultiply(float[][] a, float[][] b, float[][] result) {
int n = a.length;
int m = b[0].length;
int p = a[0].length;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j += SPECIES.length()) {
FloatVector sum = FloatVector.zero(SPECIES);
for (int k = 0; k < p; k++) {
FloatVector aVec = FloatVector.broadcast(SPECIES, a[i][k]);
FloatVector bVec = FloatVector.fromArray(SPECIES, b[k], j);
sum = aVec.fma(bVec, sum);
}
sum.intoArray(result[i], j);
}
}
}
public static void main(String[] args) {
float[][] a = {{1, 2}, {3, 4}};
float[][] b = {{5, 6}, {7, 8}};
float[][] result = new float[2][2];
matrixMultiply(a, b, result);
System.out.println("矩阵乘法结果:");
for (float[] row : result) {
System.out.println(Arrays.toString(row));
}
}
}总结
Java 21 是一个里程碑式的版本,虚拟线程的正式化彻底改变了 Java 的并发编程模式,让开发者可以用简单的同步代码编写高并发应用。Switch 模式匹配和 Record 模式的正式化让数据处理更加优雅和类型安全。
分代 ZGC 的引入为低延迟应用提供了更好的性能,有序集合接口让集合操作更加直观。作为 LTS 版本,Java 21 在稳定性和新特性之间找到了很好的平衡。
Java 21 已经成为现代 Java 开发的首选版本,其革命性的虚拟线程特性和完善的模式匹配支持,使其成为从 Java 8/11/17 升级的理想选择。