虛擬執行緒

概述

虛擬執行緒(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 ThreadVirtual 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();
}

參考資源