Java 21 虚拟线程详解

Java 21 引入了虚拟线程这一重磅特性——长期以来开发者们一直期待的“Java 协程”。在高并发和 I/O 密集型应用中,虚拟线程不仅大幅降低内存占用,还能实现高效的上下文切换,从而显著提升系统性能和吞吐量。


1. 什么是虚拟线程

虚拟线程是 JDK 21 推出的一种轻量级线程,其核心优势在于:

  • 内存占用低:无需为每个虚拟线程分配一个独立的操作系统线程,降低了系统资源消耗。
  • 高效上下文切换:由 JVM 内部调度管理,不涉及昂贵的 OS 级别线程切换,能更好地应对高并发场景。

简而言之,虚拟线程可看作是“任务(Task)”,它们运行在传统的操作系统线程之上,但在代码层面与常规线程使用方式完全一致。


2. 虚拟线程的工作原理

当应用程序启动一个虚拟线程时,JVM 会将其交由底层的线程池(由传统线程构成)执行。其核心工作机制包括:

  • 任务调度:一个传统线程可以轮流执行多个虚拟线程。举例来说,假设系统创建了 1000 个虚拟线程,而底层线程池只有 10 个传统线程,那么:

    • 初始时,V1 到 V10 分别调度到 T1 到 T10 上执行。
    • 当虚拟线程(例如 V3)因 I/O 操作而阻塞时,T3 立即释放出来去执行等待中的虚拟线程(如 V11)。
    • 如果所有虚拟线程均处于非阻塞状态,JVM 会按照时间片(例如每 100ns)轮转调度,将部分虚拟线程挂起以让新任务得以执行。
    • 如果以上条件均不满足,新任务将挂起等待空闲传统线程。
  • 阻塞处理:当虚拟线程遇到阻塞(如 I/O 操作)时,JVM 会立刻挂起该虚拟线程,并通过操作系统事件(如 epoll)通知 I/O 完成,从而在合适时机重新唤醒该虚拟线程。

这套机制确保了即使在大量 I/O 阻塞场景下,系统也不会因为传统线程资源不足而性能急剧下降。


3. 虚拟线程的调度

  • 自动调度:JDK 21 默认启用虚拟线程,且调度由 JVM 管理。默认情况下,JVM 会利用 ForkJoinPool 来执行虚拟线程,并根据实际任务数动态调整底层线程数。
  • 自定义调度:虽然大部分场景下无需手动干预,但若有特殊需求(例如控制并发量、定制线程池参数等),可自定义线程池并将虚拟线程交给该线程池执行。

4. 虚拟线程与传统线程的区别

两者的主要差异包括:

  • 线程创建方式

    • 虚拟线程:不直接创建操作系统线程,运行时由传统线程池调度。
    • 传统线程:每创建一个线程,JVM 都会启动一个独立的操作系统线程。
  • 资源消耗

    • 虚拟线程:内存开销极小,可轻松创建百万级虚拟线程。
    • 传统线程:资源开销较大,一般只能支持几千个线程。
  • 上下文切换

    • 虚拟线程:上下文切换由 JVM 管理,开销低。
    • 传统线程:依赖 OS 级调度,切换开销较高。
  • 调度与执行

    • 虚拟线程:任务调度完全由 JVM 控制,遇到阻塞时只挂起任务,不会占用底层线程。
    • 传统线程:阻塞操作会直接占用线程,影响线程池整体吞吐。

5. 虚拟线程与协程的对比

虽然虚拟线程与 Python 等语言中的协程在处理 I/O 阻塞时有相似的“让步”机制,但二者存在显著区别:

对比维度 虚拟线程 协程
并发/并行 可在多个 CPU 上并行运行,支持真正的并行执行 只有单个主线程调度,同一时刻只处理一个任务
资源争夺 存在资源竞争和状态同步问题,需要合理设计并发控制 单线程执行,无并发资源争夺问题
框架支持 JDK 21 原生支持,无需额外框架 依赖专用异步框架,编写及调试相对复杂

6. 如何使用虚拟线程

在 JDK 21 中,使用虚拟线程主要有两种方式:

6.1 直接创建并启动虚拟线程

public class VirtualThreadExample {
   
    public static void main(String[] args) {
   
        Thread virtualThread = Thread.ofVirtual().start(() -> {
   
            System.out.println("Hello virtual thread");
        });
        try {
   
            virtualThread.join(); // 等待虚拟线程完成
        } catch (InterruptedException e) {
   
            e.printStackTrace();
        }
    }
}

6.2 通过线程池执行虚拟线程

import java.util.concurrent.*;

public class VirtualThreadPoolExample {
   
    public static void main(String[] args) {
   
        // 创建一个虚拟线程池
        ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
        // 提交多个任务到线程池
        for (int i = 0; i < 10; i++) {
   
            final int taskId = i;
            executor.submit(() -> {
   
                System.out.println("Task " + taskId + " running in " + Thread.currentThread());
            });
        }
        // 关闭线程池
        executor.shutdown();
    }
}

注意:虚拟线程池不支持设置核心线程数、最大线程数或任务队列等参数。如果需要对并发量进行严格控制,可以自定义线程池。


7. 自定义虚拟线程池示例

为了避免因无限制并发导致 OOM 或对下游系统产生巨大压力,可借助信号量(Semaphore)和阻塞队列实现自定义虚拟线程池。下面是一个示例:

package com.zengbiaobiao.demo.vitrualthreaddemo;

import java
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

@程序员小袁

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值