JDK21新特性及虚拟线程

目录

1.虚拟线程

创建方式

性能优化

1. IO密集型任务

2. 异步任务编排

3. 高并发事件处理

应用场景

注意事项

1. 阻塞操作优化

2. 线程本地变量陷阱

3. 资源耗尽防护

4.不要池化虚拟线程

2.模式匹配增强

3.随机生成数改进

4.非侵入式日志

5.HTTP/2 客户端

6.泛型增强

7.并发随机数生成器

8.向量 API

1.虚拟线程

虚拟线程是一种由JVM而非操作系统管理的线程。独立于操作系统线程(也称为平台线程),具有以下核心特性:

  • 轻量级:每个虚拟线程仅需几百字节内存,与传统线程(2MB~20MB)相比,资源消耗降低99%以上。

  • 高并发:支持数百万级别的并发线程,不会因线程数量限制系统性能。

  • 与传统线程兼容:完全兼容现有的线程API,无需修改代码即可引入虚拟线程

性能对比

// 传统线程池(平台线程)  
ExecutorService executor = Executors.newFixedThreadPool(200);  
// 虚拟线程池  
ExecutorService virtualExecutor = Executors.newVirtualThreadPerTaskExecutor();  
​
// 执行10万请求  
for (inti=0; i < 100_000; i++) {  
    executor.submit(() -> handleRequest()); // 平台线程  
    virtualExecutor.submit(() -> handleRequest()); // 虚拟线程  
}  
指标 平台线程池(200线程) 虚拟线程池
内存占用 4.2GB 800MB
请求完成时间 32秒 6秒

为啥虚拟线程这么牛

传统的线程模型中,每个线程都映射到操作系统线程,但大部分时间都在等IO,造成资源浪费。而虚拟线程采用挂起-恢复机制,在等待IO时会把自己挂起,释放底层的载体线程(Carrier Thread)去干别的活。

创建方式

虚拟线程的创建方式与传统线程类似,通过Thread.ofVirtual()Executors.newVirtualThreadPerTaskExecutor()实现。

  1. 直接创建虚拟线程

public class VirtualThreadExample {
    public static void main(String[] args) throws InterruptedException {
        // 创建虚拟线程
        Thread virtualThread = Thread.ofVirtual().start(() -> {
            System.out.println("虚拟线程执行任务: " + Thread.currentThread().getName());
        });
        virtualThread.join(); // 等待虚拟线程执行完成
    }
}
//结果
//虚拟线程执行任务: VirtualThread-1
  1. 使用虚拟线程池:每个任务对应一个虚拟线程,系统资源占用极低。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
​
public class VirtualThreadPoolExample {
    public static void main(String[] args) {
        // 创建虚拟线程池
        ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
​
        // 提交大量任务
        for (int i = 1; i <= 1000; i++) {
            int taskId = i;
            executor.submit(() -> {
                System.out.println("执行任务 " + taskId + ",线程名: " + Thread.currentThread().getName());
            });
        }
        executor.shutdown(); // 关闭线程池
    }
}
//结果
执行任务 1,线程名: VirtualThread-1
执行任务 2,线程名: VirtualThread-2
执行任务 3,线程名: VirtualThread-3
...
  1. 使用try-with-resources管理线程池

虚拟线程池支持try-with-resources语法,方便资源管理。

import java.util.concurrent.Executors;
​
public class VirtualThreadPoolWithResources {
    public static void main(String[] args) {
        try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
            for (int i = 1; i <= 10; i++) {
                int taskId = i;
                executor.submit(() -> {
                    System.out.println("任务 " + taskId + " 由线程: " + Thread.currentThread().getName() + " 执行");
                });
            }
        } // 线程池自动关闭
    }
}
性能优化
1. IO密集型任务

某日志分析系统实测:处理百万条日志解析任务,虚拟线程池相比固定线程池吞吐量提升8倍。

//虚拟线程在HTTP请求、数据库操作等场景表现卓越
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {  
    for (int i = 0; i < 1_000_000; i++) {  
        executor.submit(() -> {  
            // 模拟HTTP调用  
            String response = httpClient.send(request);  
            //模拟数据库写入
            saveToDB(response);  
        });  
    }  
}  

2. 异步任务编排
  • 在复杂的业务场景中,任务通常需要多个步骤完成,例如从缓存查询数据、对数据进行加工、最后将结果返回给客户端。

  • 为了提高效率,这些步骤可以异步执行,并且通过 链式调用+虚拟线程 实现流程编排。

CompletableFuture.supplyAsync(() -> queryFromCache(), virtualExecutor)  //异步执行queryFromCache()方法,获取缓存数据
                .thenApplyAsync(data -> enrichData(data), virtualExecutor) //对上一步的结果进行加工 
                .thenAcceptAsync(result -> sendResponse(result), virtualExecutor);   //将最终结果发送给客户端
3. 高并发事件处理
  • 传统方式中,每个连接都需要一个线程来处理,线程数量受限于操作系统的能力,难以支撑大规模并发。

  • 为每个客户端连接启动一个虚拟线程,虚拟线程的开销极低,因此可以轻松支持数十万甚至上百万的并发连接。

ServerSocketChannel serverChannel = ServerSocketChannel.open().bind(new InetSocketAddress(8080));  
while (true) {  
    SocketChannel clientChannel = serverChannel.accept();   //接受客户端连接
    //为每个客户端连接启动一个虚拟线程,执行handleClient方法
    Thread.startVirtualThread(() -> handleClient(clientChannel));  
}  

单机可轻松支撑10万+并发连接。

应用场景

改造Web服务提升吞吐量

现在有个REST API服务,主要提供数据查询,每个请求都需要查询数据库、调用远程服务,典型的IO密集型应用。用传统Tomcat+线程池方式,撑死了每秒处理1000个请求。

修改Spring Boot配置,启用虚拟线程:

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
    @Bean
    public TomcatProtocolHandlerCustomizer<?> protocolHandlerVirtualThreadExecutorCustomizer() {
        return protocolHandler -> {
            protocolHandler.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
        };
    }
}

利用Java异步编程模型进一步优化(比如CompletableFuture)

@GetMapping(“/users/{id}/details”)
public UserDetailsDTO getUserDetails(@PathVariable Long id) {
    // 使用虚拟线程执行器
    try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
        CompletableFuture<UserDTO> userFuture = CompletableFuture.supplyAsync(
            () -> userService.getUser(id), executor);
            
        CompletableFuture<List<OrderDTO>> ordersFuture = CompletableFuture.supplyAsync(
            () -> orderService.getUserOrders(id), executor);
            
        // 并行执行两个任务,然后组合结果
        return CompletableFuture.allOf(userFuture, ordersFuture)
            .thenApply(v -> new UserDetailsDTO(userFuture.join(), ordersFuture.join()))
            .join();
    }
}

压测工具用的是Apache JMeter,配置了1000个并发用户,持续压测5分钟。结果简直惊呆了:

  • 平台线程版本:最大吞吐量约1,200 RPS,平均响应时间800ms

  • 虚拟线程版本:最大吞吐量约48,000 RPS,平均响应时间20ms

吞吐量提升40倍,响应时间降低40倍!这还是在相同硬件配置下(8核16G内存)

温馨提示:不要盲目用虚拟线程替换所有线程池,要根据应用特性决定。有些场景下传统线程池仍然有优势,比如需要控制并发度的场景。

注意事项
1. 阻塞操作优化

虚拟线程在synchronized块或本地方法调用时会固定(Pin)到平台线程,导致调度失效。解决方案:

  • 使用ReentrantLock替代synchronized

  • 避免在虚拟线程中调用JNI代码

// 错误示范
synchronized(lock) { database.query(); } 
​
// 正确方案
database.query(); // 虚拟线程自动挂起
synchronized(lock) { updateSharedState(); } // 同步块控制在10ms内
2. 线程本地变量陷阱

ThreadLocal在虚拟线程中可能引发内存泄漏(线程生命周期长且数量多):

try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {  
    executor.submit(() -> {  
        ThreadLocalRandom.current(); // 安全  
        MyContext.set(user); // 危险!需改用Scoped Values  
    });  
}  

Java 21引入Scoped Values(作用域值)替代ThreadLocal

3. 资源耗尽防护

无限创建虚拟线程可能导致GC压力:

// 错误示例:无限制提交任务  
ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();  
for (inti=0; i < Integer.MAX_VALUE; i++) {  
    executor.submit(() -> heavyTask()); // 可能触发OOM  
}  
​
// 正确方案:限流队列  
ExecutorService safeExecutor = new ThreadPoolExecutor(  
    1000, 1000, 60, TimeUnit.SECONDS,  
    new LinkedBlockingQueue<>(10_000),  
    Thread.ofVirtual().factory()  
);  
4.不要池化虚拟线程

虚拟线程就是用完即丢的,不需要复用。

// 错误用法
ExecutorService badExecutor = Executors.newFixedThreadPool(100, 
    Thread.ofVirtual().factory());  // 不要这样做!
​
// 正确用法
ExecutorService goodExecutor = Executors.newVirtualThreadPerTaskExecutor();

2.模式匹配增强

Java 17 引入了模式匹配,JDK 21 在此基础上改进了对 instanceof 的模式匹配支持,可以直接在 instanceof 表达式中使用类型转换。

class Example {
    void process(Object obj) {
        if (obj instanceof String s) {
            // 可以直接使用s,无需额外的类型转换
            System.out.println("String length: " + s.length());
        } else {
            System.out.println("Not a String");
        }
    }
}

3.随机生成数改进

JDK 21 对随机生成器进行了改进,引入了一些新的方法和算法,提高了其性能和质量

import java.util.Random;
public class Example {
    public static void main(String[] args) {
        Random random = new Random();
        int randomNumber = random.nextInt(100);
        System.out.println("Random number: " + randomNumber);
    }
}

4.非侵入式日志

JDK 21 提供了一种非侵入式的方式来进行 Java 日志记录,使得开发者能够更轻松地管理应用程序的日志信息。

import java.logging.Logger;
public class Example {
    //使用Logger 类来记录日志信息,无需引入额外日志框架
    private static final Logger logger = Logger.getLogger(Example.class.getName());
​
    public static void main(String[] args) {
        logger.info("This is an informational message.");
    }
}

5.HTTP/2 客户端

JDK 11 引入了原生的 HTTP 客户端,JDK 21 进一步增强了对 HTTP/2 的支持,使得开发者能够更高效地与支持 HTTP/2 协议的服务器进行通信。

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class Example {
    public static void main(String[] args) throws Exception {
        HttpClient client = HttpClient.newHttpClient();
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://example.com"))
                .build();
        HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
​
        System.out.println("Response code: " + response.statusCode());
        System.out.println("Response body: " + response.body());
    }
}

6.泛型增强

新的泛型特性包括泛型推断、泛型枚举和泛型实例方法。

// 泛型推断示例
var list = new ArrayList<String>();  // 可以省略类型参数
​
// 泛型枚举示例
enum Option<T> {
    SOME, NONE;
}
​
// 泛型实例方法示例
class Utilities {
    public <T> T getFirst(List<T> list) {
        return list.get(0);
    }
}

7.并发随机数生成器

该功能提供了一种并发安全的随机数生成器,使得开发者能够更安全地在多线程环境中生成随机数。

import java.util.concurrent.ThreadLocalRandom;
public class Example {
    public static void main(String[] args) {
        // 生成一个介于 0 和 100 之间的随机数
        int randomNumber = ThreadLocalRandom.current().nextInt(0, 101);
        System.out.println("Random number: " + randomNumber);
    }
}

8.向量 API

该功能为 Java 增加了向量 API,使得开发者能够更高效地进行向量化操作,从而提升代码的性能。

import jdk.incubator.vector.*;
public class Example {
    public static void main(String[] args) {
        VectorSpecies<Float> species = FloatVector.SPECIES_256;
        float[] a = new float[species.length()];
        float[] b = new float[species.length()];
​
        // 初始化数组
        for (int i = 0; i < species.length(); i++) {
            a[i] = i;
            b[i] = i * 2;
        }
​
        // 使用向量化操作进行数组加法
        FloatVector va = FloatVector.fromArray(species, a, 0);
        FloatVector vb = FloatVector.fromArray(species, b, 0);
        FloatVector result = va.add(vb);
​
        // 将结果写回数组
        result.intoArray(a, 0);
​
        // 打印结果数组
        for (float f : a) {
            System.out.println(f);
        }
    }
}

原文链接:JDK21新特性及虚拟线程

© 版权声明
THE END
喜欢就支持一下吧
点赞5 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容