JDK21之虚拟线程
传统线程的三个痛点:
-
重量级:每创建一个线程,操作系统都要分配栈内存、维护线程状态,成本高得很。
-
切换成本高:线程一多,CPU得频繁在线程之间切换(上下文切换),性能开销大。
-
受限于平台资源:比如 Linux 上默认线程栈大小是 1MB,意味着一个 8GB 内存的服务理论上最多跑 8000 个线程。
定义
虚拟线程是JVM层面实现的一种轻量级线程,不再直接依赖操作系统线程,而是由JVM自己调度和管理。
工作原理
虚拟线程通过将多个虚拟线程映射到少量的操作系统线程来工作。这种映射是动态的,JVM 可以在合适的时候将虚拟线程挂起或恢复,从而在物理线程之间共享资源。因此,虚拟线程的调度方式比传统的线程调度更加灵活和高效。
适用场景
虚拟线程适用于高并发的 I/O 密集型任务,例如:
- I/O 密集型任务 :线程在等待 I/O(如网络请求、数据库查询)时自动挂起,载体线程可执行其他任务。
- 高并发场景 :处理大量并发连接(如微服务、Web 服务器)。
虚拟线程适用于 I/O 密集型任务的原因
- 用户态线程 :由 JVM 管理,不直接绑定内核线程,可创建数百万个虚拟线程。
- 低资源消耗 :初始栈内存约 200B,挂起时自动释放载体线程(Carrier Thread)。
虚拟线程不适用于CPU密集型任务的原因
CPU密集型任务会持续暂用CPU,如果线程过多频繁线程切换会增加额外开销,导致整体吞吐量下降。
创建虚拟线程
方式 1:直接创建
// 创建并启动虚拟线程
Thread virtualThread = Thread.ofVirtual().start(() -> {System.out.println("Running in a virtual thread: " + Thread.currentThread());
});// 等待线程完成
virtualThread.join();
方式 2:使用 Thread.Builder
Thread.Builder builder = Thread.ofVirtual().name("my-virtual-thread-", 0);
Thread vThread = builder.start(() -> {System.out.println("Thread name: " + Thread.currentThread().getName());
});
方式 3:通过线程池(推荐)
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {for(int i = 0; i < 100; i++){// 提交任务(自动创建虚拟线程)executor.submit(() -> {System.out.println("Task running in virtual thread");});}} // 自动关闭线程池/**
* 虚拟线程在等待 I/O(如数据库、HTTP 请求)时自动挂起 ,释放底层平台线程,避免阻塞
*/
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {for (int i = 0; i < 10_000; i++) {executor.submit(() -> {// 模拟 I/O 操作(自动挂起虚拟线程)String response = httpClient.send(request, BodyHandlers.ofString());process(response);});}
}
传统线程池(平台线程)适用于 CPU 密集型任务的原因
- 内核线程绑定 :每个平台线程对应一个操作系统线程(Kernel Thread),由 OS 调度。
- 资源消耗高 :每个线程默认占用约 1MB 栈内存,大量线程导致内存压力和上下文切换开销。
- 适用场景 :
- CPU 密集型任务 :线程数通常设置为接近 CPU 核心数(
Runtime.getRuntime().availableProcessors()
),最大化利用 CPU 资源。 - 同步阻塞操作 :若线程在 I/O 或锁等待时阻塞,会导致线程资源浪费。
- CPU 密集型任务 :线程数通常设置为接近 CPU 核心数(
传统线程池的优化策略
- 固定线程数 :通过
Executors.newFixedThreadPool(nThreads)
限制线程数,避免过多线程导致资源耗尽。 - 任务队列缓冲 :使用有界/无界队列(如
LinkedBlockingQueue
)暂存任务,但队列过长可能引发高延迟或 OOM。
能否用虚拟线程替代传统线程池?
1. 可替代的场景
- 纯异步 I/O 任务 :如 HTTP 服务、数据库访问、消息队列消费等,虚拟线程显著提升吞吐量。
- 高并发低延迟需求 :虚拟线程通过减少线程切换和内存开销,更适合处理大量短暂任务。
2. 不可替代或需谨慎的场景
-
CPU 密集型任务 :
虚拟线程的载体线程数有限(默认等于 CPU 核心数),若虚拟线程执行 CPU 密集型操作,会阻塞载体线程,导致整体吞吐量下降。 -
同步代码块或 Native 方法 :
- 若虚拟线程进入
synchronized
块或调用 JNI 方法,会固定绑定到载体线程,丧失调度灵活性。 - 需改用
ReentrantLock
并配合tryLock()
实现非阻塞同步。
- 若虚拟线程进入
-
依赖
ThreadLocal
的代码 :
虚拟线程支持ThreadLocal
,但大量使用可能导致内存泄漏(因虚拟线程生命周期短且数量多)。建议改用Scoped Values
(JDK 20+)。