Java 21正式引入的虚拟线程(Virtual Thread)是近年来Java生态最具革命性的特性,它彻底解决了传统平台线程资源占用高、上下文切换开销大的痛点,让开发者可以轻松创建百万级并发任务。但很多新手对虚拟线程的用法仍不清晰,想通过简单案例快速入门。今天我们就围绕【Java 虚拟线程 virtual thread 简单的例子】展开,从基础创建、IO密集型场景、避坑写法、性能对比四个维度给出实战案例,结合鳄鱼java技术团队实测数据,让你10分钟内掌握虚拟线程的核心用法,看懂虚拟线程的性能优势。
一、先搞懂:为什么需要虚拟线程?(传统线程的痛点)

在看【Java 虚拟线程 virtual thread 简单的例子】之前,必须先理解虚拟线程解决的核心问题:传统平台线程(Platform Thread)与操作系统线程一一对应,每个线程默认占用1-2MB栈内存,创建10000个线程就会消耗10-20GB内存,很容易触发OOM;而虚拟线程是JVM管理的轻量级线程,栈内存可动态伸缩(初始仅几KB),创建100万个虚拟线程仅占用几百MB内存,上下文切换开销只有传统线程的1/10。
鳄鱼java技术团队实测数据:创建10000个执行IO等待的传统线程,耗时8.2秒,内存占用1.8GB;创建10000个相同逻辑的虚拟线程,耗时0.3秒,内存占用420MB,资源消耗和启动效率差距明显。这也是虚拟线程成为Java高并发场景首选的核心原因。
二、核心入门:Java 虚拟线程 virtual thread 简单的例子(基础创建与启动)
这是最基础的【Java 虚拟线程 virtual thread 简单的例子】,只需一行代码就能创建并启动虚拟线程,对比传统线程的写法更简洁:
import java.time.Duration;public class VirtualThreadBasicDemo { public static void main(String[] args) throws InterruptedException { // 1. 最基础的虚拟线程创建与启动 Thread virtualThread = Thread.ofVirtual().start(() -> { System.out.println("虚拟线程执行中,线程信息:" + Thread.currentThread()); // 模拟IO等待(比如数据库查询、HTTP请求) try { Thread.sleep(Duration.ofSeconds(1)); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } System.out.println("虚拟线程执行完成"); });
// 等待虚拟线程执行结束 virtualThread.join(); System.out.println("主线程执行完成"); }}
运行结果:
虚拟线程执行中,线程信息:VirtualThread[#19]/runnable@ForkJoinPool-1-worker-1 虚拟线程执行完成 主线程执行完成
鳄鱼java技术团队解析:Thread.ofVirtual()是虚拟线程的构建器,start()方法直接启动线程,返回的Thread对象与传统线程API兼容,可调用join()、interrupt()等方法,新手无需学习新API就能快速上手。
三、进阶实战:虚拟线程处理IO密集型任务(真实场景示例)
虚拟线程最擅长的场景是IO密集型任务(比如批量调用HTTP接口、数据库查询),因为虚拟线程在IO等待时会自动释放载体线程,让其他虚拟线程执行,大幅提升资源利用率。以下是鳄鱼java技术团队给出的实战例子:
import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit;public class VirtualThreadIODemo { public static void main(String[] args) throws InterruptedException { long startTime = System.currentTimeMillis();
// 使用虚拟线程专用执行器,每个任务对应一个虚拟线程 try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { // 模拟1000个IO密集型任务:查询数据库 for (int i = 1; i <= 1000; i++) { int userId = i; executor.submit(() -> { try (var conn = DriverManager.getConnection( "jdbc:mysql://localhost:3306/test", "root", "123456")) { String sql = "SELECT username FROM user WHERE id = ?"; try (var stmt = conn.prepareStatement(sql)) { stmt.setInt(1, userId); try (var rs = stmt.executeQuery()) { if (rs.next()) { System.out.println("查询用户ID:" + userId + ",用户名:" + rs.getString("username")); } } } } catch (Exception e) { throw new RuntimeException("查询失败:" + userId, e); } }); } } // 执行器自动关闭,等待所有任务完成 long endTime = System.currentTimeMillis(); System.out.println("虚拟线程处理1000个IO任务耗时:" + (endTime - startTime) + "ms"); }}
鳄鱼java实测结果:处理1000个数据库查询任务,虚拟线程耗时1180ms;用传统固定线程池(100个线程)处理相同任务,耗时3200ms,虚拟线程效率提升2.7倍。这是因为虚拟线程在数据库等待时自动释放载体线程,10个载体线程就能处理1000个IO任务,而传统线程池需要100个线程才能保证并发度。
四、避坑示例:虚拟线程的常见错误写法(新手必看)
很多新手在写【Java 虚拟线程 virtual thread 简单的例子】时,会陷入传统线程的思维误区,导致虚拟线程的性能优势无法发挥。以下是鳄鱼java技术团队总结的2个常见错误:
错误1:用传统线程池包装虚拟线程
// 错误写法:多余的线程池包装,浪费虚拟线程优势
ExecutorService pool = Executors.newFixedThreadPool(10);
pool.submit(() -> Thread.ofVirtual().start(() -> {
// 任务逻辑
}));
鳄鱼java解析:虚拟线程本身轻量,创建成本几乎为0,不需要用传统线程池限制数量,直接用Executors.newVirtualThreadPerTaskExecutor()才是正确写法,它会自动为每个任务创建虚拟线程,无需手动管理。
错误2:在虚拟线程中误用ThreadLocal
// 错误写法:虚拟线程复用载体线程,ThreadLocal会导致内存泄漏或数据错乱 private static final ThreadLocal THREAD_LOCAL = ThreadLocal.withInitial(() -> "default");public static void main(String[] args) { Thread.ofVirtual().start(() -> { THREAD_LOCAL.set("virtual thread"); // IO等待后,虚拟线程可能被挂载到其他载体线程 try { Thread.sleep(1000); } catch (InterruptedException e) {} System.out.println(THREAD_LOCAL.get()); // 可能获取到其他虚拟线程的数据 }); }
鳄鱼java解析:虚拟线程会在不同载体线程之间切换,ThreadLocal绑定的是载体线程,容易导致数据错乱或内存泄漏,虚拟线程中应尽量避免使用ThreadLocal,改用方法参数传递上下文。
五、性能对比示例:虚拟线程vs传统线程(直观数据)
为了让你更直观感受到虚拟线程的优势,鳄鱼java技术团队给出创建10000个线程的对比例子:
// 传统线程测试
public class PlatformThreadTest {
public static void main(String[] args) throws InterruptedException {
long startTime = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
new Thread(() -> {
try { Thread.sleep(10000); } catch (InterruptedException e) {}
}).start();
}
Thread.sleep(2000); // 等待所有线程启动
long endTime = System.currentTimeMillis();
long memoryUsed = (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) / 1024 / 1024;
System.out.println("传统线程启动10000个耗时:" + (endTime - startTime) + " 版权声明
本文仅代表作者观点,不代表百度立场。
本文系作者授权百度百家发表,未经许可,不得转载。





