Java 21新特性详解:虚拟线程、模式匹配、结构化并发,性能提升太明显了
Java 21是最新的LTS长期支持版本,带来了很多重磅新特性,性能大幅提升,开发效率也高了很多,现在越来越多公司已经从Java 8升级到Java 21了。本文给大家详细介绍Java 21的核心新特性,看完就知道升级有多香。
一、为什么要升级Java 21
1.1 核心优势
现在Spring Boot 3.x已经要求最低Java 17,很多大厂比如阿里、美团、字节都已经开始升级Java 21了,早晚都要升,不如早点学。
二、重磅新特性:虚拟线程(Virtual Threads)
虚拟线程是Java 21最重量级的新特性,彻底解决了Java多线程的痛点。
2.1 传统线程的问题
传统的平台线程是和操作系统内核线程一一对应的,创建成本很高,一个线程要占1M左右的栈内存,一台机器最多开几千个线程就到顶了,高并发场景很容易达到瓶颈,所以以前只能用线程池来复用线程。
2.2 虚拟线程的优势
虚拟线程是轻量级的用户态线程,由JVM管理,创建成本极低,一个虚拟线程只占几百字节的内存,一台机器随便开几十上百万个虚拟线程都没问题,不用线程池复用,直接new就完事了。
2.3 使用方法
// 1. 直接创建虚拟线程运行
Thread.startVirtualThread(() -> {
System.out.println("虚拟线程运行中:" + Thread.currentThread());
});
// 2. 用ExecutorService创建,自动创建虚拟线程
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < 10000; i++) {
int taskId = i;
executor.submit(() -> {
try {
Thread.sleep(Duration.ofSeconds(1));
System.out.println("任务" + taskId + "完成");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
}
} // 自动等待所有任务完成
上面的代码同时运行1万个任务,每个任务等待1秒,用虚拟线程总耗时只要1秒多,用传统线程的话至少要几十秒,性能差距巨大。
2.4 适用场景
三、语法简化新特性
3.1 Record类(Java 16引入,Java 21优化)
以前定义一个简单的DTO类要写一堆getter、setter、构造方法、equals、hashCode、toString,很麻烦,用Record一行就搞定:
// 定义一个用户DTO,自动生成构造方法、getter、equals、hashCode、toString
public record User(Long id, String username, String email, Integer age) {}
// 使用
public class Main {
public static void main(String[] args) {
User user = new User(1L, "张三", "zhangsan@example.com", 25);
System.out.println(user.username()); // 直接用字段名获取值,不用getXxx()
System.out.println(user); // 自动生成的toString,打印所有字段
}
}
Record是不可变类,字段都是final的,非常适合做DTO、数据传输对象,代码简洁太多了。
3.2 密封类(Sealed Classes)
密封类可以控制哪些类可以继承它,防止被滥用:
// 只有Circle、Rectangle、Triangle可以继承Shape
public sealed class Shape permits Circle, Rectangle, Triangle {}
public final class Circle extends Shape {}
public final class Rectangle extends Shape {}
public final class Triangle extends Shape {}
// 其他类继承Shape会编译报错
适合做领域模型、状态枚举,避免继承滥用。
3.3 模式匹配(Pattern Matching)
instanceof模式匹配(Java 16引入)
以前判断类型需要强转:
// 旧写法
if (obj instanceof String) {
String s = (String) obj;
System.out.println(s.length());
}
现在直接在判断的时候定义变量,不用强转:
// 新写法
if (obj instanceof String s) {
System.out.println(s.length()); // 直接用s变量
}
switch模式匹配(Java 21正式支持)
以前的switch只能匹配常量,现在可以匹配类型、null、复杂表达式:
public String format(Object obj) {
return switch (obj) {
case Integer i -> "整数:" + i;
case String s -> "字符串:" + s;
case Long l when l > 100 -> "大的长整数:" + l; // 带条件判断
case null -> "空值";
default -> "未知类型:" + obj.toString();
};
}
还可以匹配Record类型:
public double calculateArea(Shape shape) {
return switch (shape) {
case Circle(var radius) -> Math.PI * radius * radius;
case Rectangle(var width, var height) -> width * height;
case Triangle(var a, var b, var c) -> {
double p = (a + b + c) / 2;
yield Math.sqrt(p * (p - a) * (p - b) * (p - c)); // yield返回值
}
};
}
代码比以前的if-else简洁太多了,可读性也更好。
3.4 字符串模板(String Templates,预览功能)
以前拼接字符串要么用+号拼接,要么用String.format,都很麻烦,现在可以直接用字符串模板:
String name = "张三";
int age = 25;
// 直接在字符串里嵌入变量和表达式
String info = STR."姓名:\{name},年龄:\{age},明年年龄:\{age + 1}";
System.out.println(info); // 输出:姓名:张三,年龄:25,明年年龄:26
// 还可以做复杂计算
int a = 10;
int b = 20;
String result = STR."\{a} + \{b} = \{a + b}"; // 10 + 20 = 30
比以前的拼接方式方便太多了,现在是预览功能,Java 22会正式支持。
3.5 文本块(Text Blocks)
以前写多行字符串要加很多换行符和拼接符,现在用文本块:
// 多行JSON字符串,不用转义双引号,自动保留格式
String json = """
{
"id": 1,
"username": "张三",
"email": "zhangsan@example.com",
"age": 25
}
""";
写JSON、SQL、HTML的时候特别方便,格式清晰,不用转义。
四、新的API和工具
4.1 结构化并发(Structured Concurrency,预览功能)
结构化并发让多任务管理更简单,要么所有任务都成功,要么都失败,避免任务泄漏:
Response handleRequest() throws ExecutionException, InterruptedException {
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
// 并发执行两个任务
var userTask = scope.fork(this::getUser);
var orderTask = scope.fork(this::getOrder);
scope.join(); // 等待所有任务完成
scope.throwIfFailed(); // 任何一个任务失败就抛出异常
// 两个任务都成功,获取结果
User user = userTask.get();
Order order = orderTask.get();
return new Response(user, order);
}
}
如果其中一个任务失败,另一个任务会自动取消,不会有僵尸任务在后台跑,非常适合多个依赖任务的并发执行。
4.2 序列集合(Sequenced Collections)
以前Java的集合没有统一的获取首尾元素、添加删除首尾元素的方法,Java 21新增了SequencedCollection、SequencedSet、SequencedMap接口,所有有序集合都实现了这些接口:
// List
SequencedCollection list = new ArrayList<>(List.of(1, 2, 3));
list.addFirst(0); // 在开头添加
list.addLast(4); // 在末尾添加
System.out.println(list.getFirst()); // 0
System.out.println(list.getLast()); // 4
list.removeFirst();
list.removeLast();
// Map
SequencedMap map = new LinkedHashMap<>();
map.putFirst("a", 1); // 在开头插入
map.putLast("c", 3); // 在末尾插入
System.out.println(map.firstEntry()); // a=1
System.out.println(map.lastEntry()); // c=3
统一了有序集合的API,不用再区分是List还是Deque了。
4.3 新的HTTP客户端
Java 11引入的HttpClient在Java 21优化了很多,原生支持异步、HTTP/2、WebSocket,不用再依赖Apache HttpClient了:
// 同步请求
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/users"))
.timeout(Duration.ofSeconds(10))
.build();
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode());
System.out.println(response.body());
// 异步请求
client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenApply(HttpResponse::body)
.thenAccept(System.out::println);
非常好用,性能也不错。
五、性能优化
5.1 ZGC垃圾回收器改进
ZGC是低延迟垃圾回收器,Java 21里ZGC的最大停顿时间不超过1ms,支持TB级别的堆内存,比G1性能好很多,现在已经是生产就绪状态,启动的时候加参数启用:
java -XX:+UseZGC -Xmx16g MyApp
5.2 AOT编译(GraalVM)
Java 21支持提前编译成本地机器码,启动速度快,内存占用低,特别适合云原生、Serverless场景,用GraalVM可以把Java程序编译成exe可执行文件,启动时间从几秒降到几毫秒。
5.3 其他优化
六、升级注意事项
6.1 兼容性问题
6.2 升级步骤
1. 本地先升级JDK到21,运行单元测试,看有没有报错
2. 升级依赖库到最新版本,支持Java 21
3. 测试环境部署运行,观察性能和稳定性
4. 灰度上线,慢慢切流量
6.3 建议
Java 21是近年来改动最大的一个LTS版本,不管是性能还是开发效率都提升了很多,是Java开发者必须掌握的版本,还在坚持用Java 8的朋友可以试试升级,肯定会真香的!