1. 如何在实践中创建虚拟线程
该从理论转向实践了!你已经知道传统方式如何创建线程:
Thread t = new Thread(() -> System.out.println("Hello from thread!"));
t.start();
或者更简洁:
new Thread(() -> System.out.println("Hi!")).start();
现在在 Java 21 中,我们有了新的方式:
Thread.startVirtualThread(() -> System.out.println("Hello from virtual thread!"));
或者更显式地:
Thread t = Thread.ofVirtual().start(() -> System.out.println("Hello from virtual thread!"));
有什么区别?
- Thread.ofVirtual().start(...) 创建 虚拟线程(Virtual Thread),由 JVM 管理,而不是由操作系统管理。
- Thread.ofPlatform().start(...)(或 new Thread(...))——像以前一样的经典平台线程。
为什么这很重要?
虚拟线程可以按数万的规模创建,而不必担心 OutOfMemoryError。现在,如果你突然想处理上百万个请求——Java 会说:“没问题,再来点!”
2. 创建虚拟线程的语法
基础示例:
public class VirtualThreadDemo {
public static void main(String[] args) {
Thread thread = Thread.ofVirtual().start(() -> {
System.out.println("来自虚拟线程的问候!线程: " + Thread.currentThread());
});
// 等待线程结束(避免 main 提前退出)
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
发生了什么?
- 我们通过 Thread.ofVirtual().start(...) 创建一个虚拟线程。
- 在线程内部执行一个简单动作:打印一条消息。
- 最后调用 thread.join(),让主线程等待虚拟线程结束(否则程序可能在它来得及输出之前就结束)。
请注意:
虚拟线程看起来并且行为上几乎与普通线程一致,但其内部是 JVM 的“魔法”!
3. 批量创建虚拟线程:Loom 的威力实战
现在来尝试一件对普通线程而言很冒险(或者几乎不可能)的事:创建 10_000 个虚拟线程,每个线程打印自己的编号。
public class VirtualThreadMassive {
public static void main(String[] args) throws InterruptedException {
int N = 10_000;
Thread[] threads = new Thread[N];
for (int i = 0; i < N; i++) {
int threadNum = i;
threads[i] = Thread.ofVirtual().start(() -> {
System.out.println("虚拟线程 #" + threadNum + " 正在工作!");
});
}
// 等待所有线程结束
for (Thread t : threads) {
t.join();
}
System.out.println("所有虚拟线程都已结束!");
}
}
- 对于普通线程(new Thread(...)),这样的代码几乎肯定会因 OutOfMemoryError 让你的程序“崩掉”。
- 而对虚拟线程来说——这就是常态!JVM 可以轻松处理成千上万甚至数以万计的线程。
顺便说一句,如果你觉得 10_000 已经很多,不妨试试 100_000,甚至 1_000_000。在现代机器上,只要你的线程做的是简单工作或主要处于输入/输出等待,JVM 也能应对。
4. Runnable 与 Lambda:如何把代码交给虚拟线程
虚拟线程与普通线程一样通过接口 Runnable 接收任务。这意味着你可以传入 Lambda 表达式、方法引用,或任何实现了 Runnable 的对象。
Lambda 示例:
Thread.ofVirtual().start(() -> System.out.println("虚拟线程中的 Lambda!"));
方法引用示例:
public class TaskRunner {
public static void main(String[] args) {
Thread.ofVirtual().start(TaskRunner::doWork);
}
static void doWork() {
System.out.println("正在虚拟线程中工作: " + Thread.currentThread());
}
}
匿名类示例:
Thread.ofVirtual().start(new Runnable() {
@Override
public void run() {
System.out.println("虚拟线程中的匿名类!");
}
});
结论:
凡是适用于普通线程的,用在虚拟线程上一样行——只是现在更“轻量又迅速”。
5. 与 ExecutorService 的对比:旧思路与新方式
传统的 ExecutorService
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 100; i++) {
int taskNum = i;
executor.submit(() -> {
System.out.println("任务 #" + taskNum + " 正在执行");
});
}
executor.shutdown();
问题:
如果任务太多而线程太少——任务会在队列中等待。如果线程太多——程序会因资源不足而“呛住”。
新方式:基于虚拟线程的 Executor
ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
for (int i = 0; i < 100_000; i++) {
int taskNum = i;
executor.submit(() -> {
System.out.println("虚拟任务 #" + taskNum);
});
}
executor.shutdown();
发生了什么?
- 每个任务都会创建一个独立的虚拟线程。
- JVM 自行调度它们,而不会让系统过载。
- 无需限制池的大小——虚拟线程“几乎零成本”。
何时使用基于虚拟线程的 Executor?
- 当你有大量任务流入,并且不想费心考虑池的大小。
- 当任务彼此独立,可以并行执行。
- 当你希望简单一些:无需手动管理线程。
6. 实用建议:该用哪种方式
何时直接使用 Thread.ofVirtual().start()?
- 当需要为某个独立的任务创建单独线程(例如用于测试、演示或简单实验)。
- 当线程数量不多,并且你希望手动管理它们。
何时使用 Executors.newVirtualThreadPerTaskExecutor()?
- 当需要批量启动任务(例如处理大量请求、文件或网络连接)。
- 当任务彼此独立,不需要相互协调。
- 当你想把虚拟线程集成进已有使用 ExecutorService 的架构(例如在 Web 服务器、任务处理器等场景)。
建议:
如果拿不准——就从基于虚拟线程的 Executor 开始。这是最通用、最现代的做法。
7. 在虚拟线程中处理异常
从 try-catch 的角度看,虚拟线程就是普通线程。如果你的 Runnable 内抛出异常,它不会“搞崩”整个 JVM,而只会让该线程以错误结束。
示例:
Thread t = Thread.ofVirtual().start(() -> {
throw new RuntimeException("出了点问题!");
});
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程继续工作。");
在 ExecutorService 中:
如果通过 submit 提交任务,可以通过 Future 获取结果;调用 get() 时会抛出异常:
ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
Future<?> f = executor.submit(() -> {
throw new RuntimeException("虚拟任务中的错误");
});
try {
f.get();
} catch (ExecutionException e) {
System.out.println("捕获到来自虚拟线程的错误: " + e.getCause());
}
executor.shutdown();
8. 创建虚拟线程时的常见错误
错误 1:混淆虚拟线程和平台线程。 如果你通过 new Thread(...) 或 Thread.ofPlatform() 创建线程,那就不是虚拟线程。只有 Thread.ofVirtual().start(...) 或 Executors 中的方法才会给你真正的虚拟线程(Virtual Threads)。
错误 2:期望对重计算任务提速。 虚拟线程不会加速严重占用 CPU 的任务(CPU-bound)。如果你有一百万个线程,每个都把圆周率算到第百万位——JVM 并不能“加速”计算,它只会在这些线程之间切换。
错误 3:为每个线程都持有资源(例如数据库)。 如果你创建了一百万个虚拟线程,但每个都需要一个独立的数据库连接——数据库会吃不消。虚拟线程适合那种大部分时间都在等待(I/O),而不是大量占用受限外部资源的任务。
错误 4:在需要时却不等待线程结束。 如果主线程比虚拟线程先结束——程序可能在结果产生前就退出。请使用 join() 或者使用带有 shutdown() 和 awaitTermination() 的 ExecutorService。
错误 5:使用与虚拟线程不兼容的过时库。 某些第三方库可能在操作系统层面阻塞线程,或使用 native 同步,从而降低虚拟线程的效率。务必检查兼容性。
GO TO FULL VERSION