虚拟线程(Virtual Threads)使用指南(Java 21+)
虚拟线程是 Java 21 正式引入的轻量级用户态线程,核心优势是低开销、高并发、支持同步编程模型实现异步性能。下面将从前提准备、基础用法、进阶用法、实战场景、注意事项五个方面详细讲解具体使用方式。
一、使用前提
- Java 版本要求:必须使用 Java 21 及以上版本(Java 19/20 为预览特性,需通过
--enable-preview启用,不推荐生产环境使用)。
编译运行说明:若使用 Java 19/20(预览版),编译和运行时需添加--enable-preview参数:
# 编译 javac --enable-preview -source 20 VirtualThreadDemo.java # 运行 java --enable-preview VirtualThreadDemo 环境验证:通过以下命令验证 Java 版本,确保满足要求:
java -version # 预期输出(示例):openjdk version "21.0.1" 2023-10-17 LTS 二、基础用法:创建与启动虚拟线程
虚拟线程的创建和启动方式简洁,支持多种写法,核心 API 位于java.lang.Thread类中。
1. 方式 1:直接启动(静态方法快捷创建)
使用Thread.startVirtualThread(Runnable)直接创建并启动虚拟线程,无需手动调用start()方法,是最简单的使用方式。
public class VirtualThreadBasicDemo1 { public static void main(String[] args) throws InterruptedException { // 1. 启动单个虚拟线程 Thread.startVirtualThread(() -> { // 虚拟线程执行的业务逻辑(支持同步阻塞操作) System.out.printf("虚拟线程1执行中,线程ID:%s,是否为虚拟线程:%b%n", Thread.currentThread().threadId(), Thread.currentThread().isVirtual()); try { // 模拟I/O阻塞(虚拟线程阻塞时会释放载体线程,不占用内核资源) Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("虚拟线程1执行完成"); }); // 2. 批量启动多个虚拟线程(支持百万级并发,资源消耗极低) for (int i = 2; i <= 10; i++) { int threadNum = i; Thread.startVirtualThread(() -> { System.out.printf("虚拟线程%d执行中,线程ID:%s%n", threadNum, Thread.currentThread().threadId()); try { Thread.sleep(500); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.printf("虚拟线程%d执行完成%n", threadNum); }); } // 主线程等待所有虚拟线程执行完成(实际开发中无需手动等待,虚拟线程由JVM管理) Thread.sleep(2000); System.out.println("所有虚拟线程执行完毕,主线程退出"); } } 2. 方式 2:手动创建 + 启动(灵活配置线程属性)
通过Thread.ofVirtual()创建Thread.Builder,可配置虚拟线程名称、优先级等属性,再通过start()启动,适合需要自定义线程属性的场景。
public class VirtualThreadBasicDemo2 { public static void main(String[] args) throws InterruptedException { // 1. 创建虚拟线程构建器,配置线程名称 Thread virtualThread = Thread.ofVirtual() .name("business-virtual-thread-1") // 设置线程名称,便于调试 .unstarted(() -> { // 定义任务逻辑,暂不启动 System.out.printf("自定义虚拟线程执行中,线程名称:%s,是否为虚拟线程:%b%n", Thread.currentThread().getName(), Thread.currentThread().isVirtual()); try { // 模拟数据库查询阻塞 Thread.sleep(800); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("自定义虚拟线程执行完成"); }); // 2. 手动启动虚拟线程 virtualThread.start(); // 3. 链式创建并启动(简化写法) Thread.ofVirtual() .name("business-virtual-thread-2") .start(() -> { System.out.printf("链式创建虚拟线程执行中,线程名称:%s%n", Thread.currentThread().getName()); try { Thread.sleep(600); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("链式创建虚拟线程执行完成"); }); // 主线程等待 Thread.sleep(1500); System.out.println("主线程退出"); } } 3. 方式 3:通过 Future 获取执行结果(有返回值任务)
若虚拟线程执行的任务需要返回结果,可使用ExecutorService配合Callable,通过Future获取返回值,这是实际开发中处理有结果任务的常用方式。
import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class VirtualThreadFutureDemo { public static void main(String[] args) throws InterruptedException, ExecutionException { // 创建虚拟线程池(每个任务对应一个虚拟线程) try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { // 1. 提交有返回值的任务(Callable) Future<String> future1 = executor.submit(new Callable<String>() { @Override public String call() throws Exception { System.out.printf("任务1执行中,虚拟线程名称:%s%n", Thread.currentThread().getName()); Thread.sleep(1000); // 模拟I/O阻塞 return "任务1执行结果:成功"; } }); // 2. 提交Lambda表达式形式的Callable任务(简化写法) Future<Integer> future2 = executor.submit(() -> { System.out.printf("任务2执行中,虚拟线程名称:%s%n", Thread.currentThread().getName()); Thread.sleep(800); return 100 + 200; // 业务计算逻辑 }); // 3. 获取任务返回结果(阻塞获取,若需非阻塞可使用isDone()判断) String result1 = future1.get(); Integer result2 = future2.get(); System.out.println("任务1返回结果:" + result1); System.out.println("任务2返回结果:" + result2); } // try-with-resources会自动关闭executor,无需手动shutdown() System.out.println("所有任务执行完毕,主线程退出"); } } 三、进阶用法:虚拟线程池的使用
实际开发中,不推荐手动创建大量虚拟线程,而是使用虚拟线程池管理任务,Java 提供了专门的虚拟线程池实现,核心是Executors.newVirtualThreadPerTaskExecutor()。
1. 核心特性
- 每个任务对应一个独立的虚拟线程,任务执行完毕后虚拟线程自动销毁,无需复用。
- 无需配置核心线程数、最大线程数,JVM 自动管理载体线程(默认载体线程数等于 CPU 核心数)。
- 实现了
AutoCloseable接口,可通过try-with-resources自动关闭,避免资源泄漏。 - 支持批量提交任务、定时任务(需配合
ScheduledExecutorService,Java 21 暂未提供原生虚拟线程定时线程池,可通过第三方库或自定义实现)。
2. 批量任务处理示例
import java.util.List; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.stream.IntStream; public class VirtualThreadPoolBatchDemo { public static void main(String[] args) { // 创建虚拟线程池 try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { // 批量提交1000个任务(支持百万级任务,内存占用仅几十MB) List<Runnable> tasks = IntStream.range(0, 1000) .mapToObj(i -> (Runnable) () -> { System.out.printf("批量任务%d执行中,虚拟线程ID:%s%n", i, Thread.currentThread().threadId()); try { // 模拟每个任务的I/O阻塞(如数据库查询、HTTP调用) TimeUnit.MILLISECONDS.sleep(200); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException(e); } System.out.printf("批量任务%d执行完成%n", i); }) .toList(); // 提交所有任务 tasks.forEach(executor::submit); // 等待所有任务执行完成(可选,executor关闭时会等待任务执行完毕) System.out.println("所有批量任务已提交,等待执行完成..."); } // 自动关闭线程池,等待所有任务执行完毕 System.out.println("批量任务全部处理完成,主线程退出"); } } 3. 定时任务处理(Java 21+ 变通方案)
Java 21 暂未提供ScheduledExecutorService的虚拟线程实现,可通过 “平台线程定时调度 + 虚拟线程执行任务” 的方式实现定时任务的高并发处理:
import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; public class VirtualThreadScheduledDemo { public static void main(String[] args) throws InterruptedException { // 1. 平台线程池(用于定时调度,仅需少量线程) ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); // 2. 虚拟线程池(用于执行实际定时任务,支撑高并发) try (var taskExecutor = Executors.newVirtualThreadPerTaskExecutor()) { // 3. 定时任务:每1秒执行一次,延迟1秒启动 scheduler.scheduleAtFixedRate(() -> { // 提交任务到虚拟线程池执行 taskExecutor.submit(() -> { System.out.printf("定时任务执行中,时间:%s,虚拟线程名称:%s%n", System.currentTimeMillis(), Thread.currentThread().getName()); try { TimeUnit.MILLISECONDS.sleep(500); // 模拟任务执行 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }); }, 1, 1, TimeUnit.SECONDS); // 主线程等待10秒后关闭定时调度 TimeUnit.SECONDS.sleep(10); scheduler.shutdown(); System.out.println("定时调度已关闭"); } System.out.println("虚拟线程池已关闭,主线程退出"); } } 四、实战场景:Spring Boot 中使用虚拟线程
Spring Boot 3.2+ 正式支持虚拟线程,可轻松将 Web 服务、异步任务切换为虚拟线程,无需修改业务代码。
1. 环境准备
- Spring Boot 版本:3.2.0 及以上
- Java 版本:21 及以上
依赖配置(pom.xml):xml
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.2.0</version> <relativePath/> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> 2. 配置 Web 服务使用虚拟线程
修改application.properties或application.yml,将 Tomcat/Jetty 的线程池切换为虚拟线程:
yaml:
# application.yml spring: # 配置Tomcat使用虚拟线程 tomcat: threads: virtual: true # 或配置Jetty使用虚拟线程(若使用Jetty容器) # jetty: # threads: # virtual: true 3. 业务代码示例(无需修改,直接受益于虚拟线程)
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.util.concurrent.TimeUnit; @SpringBootApplication @RestController public class SpringBootVirtualThreadDemo { public static void main(String[] args) { SpringApplication.run(SpringBootVirtualThreadDemo.class, args); } // 高并发接口,自动使用虚拟线程处理 @GetMapping("/api/hello") public String hello() throws InterruptedException { // 模拟I/O阻塞(如数据库查询、RPC调用) TimeUnit.MILLISECONDS.sleep(200); return String.format("Hello, Virtual Thread! 线程名称:%s,是否为虚拟线程:%b", Thread.currentThread().getName(), Thread.currentThread().isVirtual()); } } 4. 异步任务使用虚拟线程
通过@Async注解结合虚拟线程池,实现异步任务的高并发处理:
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; @SpringBootApplication @EnableAsync // 开启异步注解支持 @RestController public class SpringBootVirtualThreadAsyncDemo { public static void main(String[] args) { SpringApplication.run(SpringBootVirtualThreadAsyncDemo.class, args); } // 配置虚拟线程池作为异步任务执行器 @Bean("virtualThreadExecutor") public Executor virtualThreadExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); // 设置任务提交时使用虚拟线程 executor.setThreadFactory(Thread.ofVirtual().name("async-virtual-thread-").factory()); executor.setTaskDecorator(runnable -> runnable); executor.initialize(); return executor; } // 异步方法,使用虚拟线程池执行 @Async("virtualThreadExecutor") public void asyncTask() throws InterruptedException { System.out.printf("异步任务执行中,线程名称:%s,是否为虚拟线程:%b%n", Thread.currentThread().getName(), Thread.currentThread().isVirtual()); TimeUnit.MILLISECONDS.sleep(500); System.out.println("异步任务执行完成"); } // 调用异步任务的接口 @GetMapping("/api/async") public String async() throws InterruptedException { asyncTask(); return "异步任务已提交"; } } 五、使用注意事项
- 避免 CPU 密集型任务:虚拟线程的优势是处理阻塞式 I/O 任务,CPU 密集型任务(如大数据计算、加密解密)应使用平台线程池(线程数 = CPU 核心数 / 核心数 * 2),避免虚拟线程切换开销抵消性能收益。
- 谨慎使用 ThreadLocal:虚拟线程数量可轻松达到百万级,若每个虚拟线程通过 ThreadLocal 存储大对象,会导致内存急剧膨胀;同时,虚拟线程复用可能引发 ThreadLocal 泄漏,建议使用
InheritableThreadLocal(仅适用于父子线程传递)或避免在虚拟线程中大量使用 ThreadLocal。 - 不要手动管理虚拟线程生命周期:虚拟线程无需手动调用
join()、interrupt()(除非特殊场景),由 JVM 自动管理,手动干预可能导致资源泄漏。 - 避免长时间持有锁:虚拟线程高并发下,长时间持有锁(如
synchronized、ReentrantLock)会加剧锁竞争,导致载体线程阻塞,建议使用无锁编程(CAS)或细粒度锁。 - 调试与监控:虚拟线程支持传统调试工具(IDEA、Jstack),使用
jstack <pid>可查看虚拟线程状态(标记为VirtualThread);监控工具(Prometheus、SkyWalking)需升级到支持 Java 21 的版本,确保能正确采集虚拟线程指标。 - 资源限制:虽然虚拟线程开销低,但仍需避免无限制提交任务(如百万级任务同时提交),可通过限流组件(如 Sentinel)控制任务提交速率,防止业务逻辑异常导致的任务积压。
六、总结
- 核心创建方式:快速使用用
Thread.startVirtualThread(),自定义属性用Thread.ofVirtual(),有返回值任务用Executors.newVirtualThreadPerTaskExecutor()+Future。 - 线程池首选:实际开发优先使用
Executors.newVirtualThreadPerTaskExecutor()虚拟线程池,无需配置线程数,支持自动关闭。 - 实战落地:Spring Boot 3.2 + 可通过简单配置实现 Web 服务和异步任务的虚拟线程改造,无需修改业务代码。
- 使用边界:仅适用于阻塞式 I/O 任务、高并发轻量任务,避免 CPU 密集型任务和过度使用 ThreadLocal。