Java 21新特性详解:虚拟线程、模式匹配、结构化并发,性能提升太明显了

Java 21新特性详解:虚拟线程、模式匹配、结构化并发,性能提升太明显了

Java 21新特性详解:虚拟线程、模式匹配、结构化并发,性能提升太明显了

Java 21是最新的LTS长期支持版本,带来了很多重磅新特性,性能大幅提升,开发效率也高了很多,现在越来越多公司已经从Java 8升级到Java 21了。本文给大家详细介绍Java 21的核心新特性,看完就知道升级有多香。


一、为什么要升级Java 21

1.1 核心优势

  • **性能提升**:虚拟线程大幅降低线程开销,高并发场景吞吐量提升几倍。
  • **开发效率高**:很多语法糖简化代码,比如record、模式匹配、字符串模板,代码量减少30%。
  • **稳定性好**:LTS版本支持8年长期维护,生产环境用着放心。
  • **新功能多**:虚拟线程、结构化并发、序列集合等新特性,解决了很多老版本的痛点。
  • **兼容性好**:绝大多数Java 8的代码不用改就能在Java 21上跑,升级成本很低。
  • 现在Spring Boot 3.x已经要求最低Java 17,很多大厂比如阿里、美团、字节都已经开始升级Java 21了,早晚都要升,不如早点学。


    二、重磅新特性:虚拟线程(Virtual Threads)

    虚拟线程是Java 21最重量级的新特性,彻底解决了Java多线程的痛点。

    2.1 传统线程的问题

    传统的平台线程是和操作系统内核线程一一对应的,创建成本很高,一个线程要占1M左右的栈内存,一台机器最多开几千个线程就到顶了,高并发场景很容易达到瓶颈,所以以前只能用线程池来复用线程。

    2.2 虚拟线程的优势

    虚拟线程是轻量级的用户态线程,由JVM管理,创建成本极低,一个虚拟线程只占几百字节的内存,一台机器随便开几十上百万个虚拟线程都没问题,不用线程池复用,直接new就完事了。

    2.3 使用方法

    java
    // 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 适用场景

  • 高并发IO密集型应用:比如Web服务、微服务、网关、消息消费
  • 有大量阻塞操作的场景:比如数据库查询、网络请求、外部API调用
  • 不用改太多代码,只要把线程池换成虚拟线程的就行,就能获得几倍的性能提升
  • 注意:CPU密集型任务用虚拟线程没用,反而会有调度开销,还是用传统平台线程合适。

    三、语法简化新特性

    3.1 Record类(Java 16引入,Java 21优化)

    以前定义一个简单的DTO类要写一堆getter、setter、构造方法、equals、hashCode、toString,很麻烦,用Record一行就搞定:

    java
    // 定义一个用户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)

    密封类可以控制哪些类可以继承它,防止被滥用:

    java
    // 只有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引入)

    以前判断类型需要强转:

    java
    // 旧写法
    if (obj instanceof String) {
        String s = (String) obj;
        System.out.println(s.length());
    }
    

    现在直接在判断的时候定义变量,不用强转:

    java
    // 新写法
    if (obj instanceof String s) {
        System.out.println(s.length()); // 直接用s变量
    }
    

    switch模式匹配(Java 21正式支持)

    以前的switch只能匹配常量,现在可以匹配类型、null、复杂表达式:

    java
    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类型:

    java
    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,都很麻烦,现在可以直接用字符串模板:

    java
    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)

    以前写多行字符串要加很多换行符和拼接符,现在用文本块:

    java
    // 多行JSON字符串,不用转义双引号,自动保留格式
    String json = """
        {
            "id": 1,
            "username": "张三",
            "email": "zhangsan@example.com",
            "age": 25
        }
        """;
    

    写JSON、SQL、HTML的时候特别方便,格式清晰,不用转义。


    四、新的API和工具

    4.1 结构化并发(Structured Concurrency,预览功能)

    结构化并发让多任务管理更简单,要么所有任务都成功,要么都失败,避免任务泄漏:

    java
    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接口,所有有序集合都实现了这些接口:

    java
    // 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了:

    java
    // 同步请求
    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性能好很多,现在已经是生产就绪状态,启动的时候加参数启用:

    bash
    java -XX:+UseZGC -Xmx16g MyApp
    

    5.2 AOT编译(GraalVM)

    Java 21支持提前编译成本地机器码,启动速度快,内存占用低,特别适合云原生、Serverless场景,用GraalVM可以把Java程序编译成exe可执行文件,启动时间从几秒降到几毫秒。

    5.3 其他优化

  • 字符串、数组的操作性能提升
  • Lambda性能优化
  • 类加载速度提升,启动更快
  • 向量API,利用CPU SIMD指令提升计算性能

  • 六、升级注意事项

    6.1 兼容性问题

  • 绝大多数Java 8的代码不用改就能运行,少数用到了内部API的代码可能会有问题,比如sun.misc.Unsafe
  • 第三方库要升级到支持Java 21的版本,Spring Boot 3.x、MyBatis 3.5+都已经支持
  • 模块化系统如果没用过的话可以不用管,默认不开启也能用
  • 6.2 升级步骤

    1. 本地先升级JDK到21,运行单元测试,看有没有报错

    2. 升级依赖库到最新版本,支持Java 21

    3. 测试环境部署运行,观察性能和稳定性

    4. 灰度上线,慢慢切流量

    6.3 建议

  • 新项目直接用Java 21,享受新特性和性能提升
  • 老项目如果没什么历史包袱,建议尽早升级,长期来看收益很大
  • 不用的新特性可以不学,先从虚拟线程、Record、模式匹配这些能明显提升开发效率的特性开始用
  • Java 21是近年来改动最大的一个LTS版本,不管是性能还是开发效率都提升了很多,是Java开发者必须掌握的版本,还在坚持用Java 8的朋友可以试试升级,肯定会真香的!

    © 版权声明

    相关文章

    暂无评论

    none
    暂无评论...