虛擬執行緒
概述
虛擬執行緒(Virtual Threads)是 Project Loom 引入的輕量級執行緒實現,從 JDK 21 開始正式可用。
JVM 透過
java.lang.Thread 類型為我們提供了作業系統執行緒的抽象。每個 Platform Thread 是 1-to-1 映射到 OS/Kernel Thread。執行緒類型
Platform Thread(平台執行緒)
- 由 OS Thread 執行
- 每個 Platform Thread 對應一個 OS Thread
- 建立和切換成本較高
Virtual Thread(虛擬執行緒)
- 由 Platform Thread 執行
- 多個 Virtual Thread 可以共享同一個 Platform Thread
- 所有 Virtual Thread 的記憶體管理都在 Heap 中,就像普通物件一樣
flowchart TB
subgraph VT[Virtual Threads]
V1[VT 1]
V2[VT 2]
V3[VT 3]
V4[VT 4]
end
subgraph PT[Platform Threads]
P1[PT 1]
P2[PT 2]
end
subgraph OS[OS Threads]
O1[OS Thread 1]
O2[OS Thread 2]
end
V1 & V2 --> P1
V3 & V4 --> P2
P1 --> O1
P2 --> O2
為什麼需要虛擬執行緒?
傳統模型的問題
在 Thread-per-request 模型中,每個請求都需要一個 OS Thread:
flowchart LR
PT[Platform Thread] ==> OS[Kernel Thread]
問題:
- OS Thread 會在 Memory blocked/IO 等待時被不必要地鎖住
- 浪費系統資源
- 限制了應用程式的併發能力
虛擬執行緒的解決方案
虛擬執行緒在不執行實際工作時,會作為 stack chunk object 駐留在 Java Heap 記憶體區域中。
Virtual Threads 只有在執行實際工作時才會映射到 Platform Threads。OS Threads 在需要執行實際工作之前不會分配給 Platform Threads。
建立虛擬執行緒
使用 Thread.ofVirtual()
VirtualThreadBasic.java
Thread vThread = Thread.ofVirtual()
.name("virtual-thread-1")
.start(() -> {
System.out.println("Hello from virtual thread!");
});使用 ExecutorService
VirtualThreadExecutor.java
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
executor.submit(() -> {
System.out.println("Task running on virtual thread");
});
}效能比較
| 特性 | Platform Thread | Virtual Thread |
|---|---|---|
| 建立成本 | 高(需要 OS 資源) | 低(純 JVM 物件) |
| 記憶體使用 | 約 1MB stack | 約幾 KB |
| 切換成本 | 高(OS context switch) | 低(JVM 內部切換) |
| 最大數量 | 受限於 OS(通常數千) | 可達數百萬 |
使用場景
適合使用虛擬執行緒
- IO 密集型任務:HTTP 請求、資料庫查詢、檔案操作
- 高併發服務:Web 伺服器處理大量請求
- 長時間等待:等待外部服務回應
不適合使用虛擬執行緒
- CPU 密集型任務:數學計算、影像處理
- 需要執行緒親和性:某些 Native 程式庫
- 使用 synchronized 區塊的長時間操作
注意事項
虛擬執行緒在執行 synchronized 區塊或呼叫 native 方法時會「pin」到 Platform Thread,無法讓出執行權。建議使用
ReentrantLock 替代 synchronized。PreferReentrantLock.java
// 不推薦:可能導致 virtual thread pinning
synchronized (lock) {
// 長時間 IO 操作
}
// 推薦:使用 ReentrantLock
lock.lock();
try {
// 長時間 IO 操作
} finally {
lock.unlock();
}與現有程式碼整合
虛擬執行緒完全相容現有的 Thread API,可以無縫整合:
MigrateToVirtual.java
// 舊程式碼
ExecutorService executor = Executors.newFixedThreadPool(100);
// 遷移到虛擬執行緒
ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();Structured Concurrency(結構化併發)
JDK 21 也引入了結構化併發的預覽功能,配合虛擬執行緒使用:
StructuredConcurrency.java
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Future<String> task1 = scope.fork(() -> fetchData1());
Future<String> task2 = scope.fork(() -> fetchData2());
scope.join(); // 等待所有任務完成
scope.throwIfFailed(); // 如有異常則拋出
return task1.resultNow() + task2.resultNow();
}