跳到主要内容Java 21 虚拟线程(Virtual Threads)使用指南 | 极客日志Javajava
Java 21 虚拟线程(Virtual Threads)使用指南
详细介绍 Java 21 引入的虚拟线程技术。内容包括前提准备、基础用法(直接启动、手动创建、Future 获取结果)、进阶用法(虚拟线程池、定时任务变通方案)以及 Spring Boot 实战集成。重点强调虚拟线程适用于高并发 I/O 场景,需注意避免 CPU 密集型任务、ThreadLocal 内存泄漏及锁竞争问题,并提供相关代码示例与最佳实践建议。
漫步7 浏览 虚拟线程是 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 版本,确保满足要求:
二、基础用法:创建与启动虚拟线程
虚拟线程的创建和启动方式简洁,支持多种写法,核心 API 位于 java.lang.Thread 类中。
1. 方式 1:直接启动(静态方法快捷创建)
使用 Thread.startVirtualThread(Runnable) 直接创建并启动虚拟线程,无需手动调用 start() 方法,是最简单的使用方式。
public class VirtualThreadBasicDemo1 {
public static void main(String[] args) throws InterruptedException {
Thread.startVirtualThread(() -> {
System.out.printf("虚拟线程 1 执行中,线程 ID:%s,是否为虚拟线程:%b%n",
Thread.currentThread().threadId(), Thread.currentThread().isVirtual());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("虚拟线程 1 执行完成");
});
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);
});
}
Thread.sleep(2000);
System.out.println("所有虚拟线程执行完毕,主线程退出");
}
}
2. 方式 2:手动创建 + 启动(灵活配置线程属性)
通过 Thread.ofVirtual() 创建 Thread.Builder,可配置虚拟线程名称、优先级等属性,再通过 start() 启动,适合需要自定义线程属性的场景。
public class VirtualThreadBasicDemo2 {
public static void main(String[] args) throws InterruptedException {
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("自定义虚拟线程执行完成");
});
virtualThread.start();
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()) {
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);
return "任务 1 执行结果:成功";
}
});
Future<Integer> future2 = executor.submit(() -> {
System.out.printf("任务 2 执行中,虚拟线程名称:%s%n", Thread.currentThread().getName());
Thread.sleep(800);
return 100 + 200;
});
String result1 = future1.get();
Integer result2 = future2.get();
System.out.println("任务 1 返回结果:" + result1);
System.out.println("任务 2 返回结果:" + result2);
}
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()) {
List<Runnable> tasks = IntStream.range(0, 1000)
.mapToObj(i -> (Runnable) () -> {
System.out.printf("批量任务%d执行中,虚拟线程 ID:%s%n", i, Thread.currentThread().threadId());
try {
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);
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 {
ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
try (var taskExecutor = Executors.newVirtualThreadPerTaskExecutor()) {
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);
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 及以上
<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 的线程池切换为虚拟线程:
spring:
tomcat:
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 {
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。
微信扫一扫,关注极客日志
微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具
- Keycode 信息
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
- Escape 与 Native 编解码
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
- JavaScript / HTML 格式化
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
- JavaScript 压缩与混淆
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online