OpenJDK 的 功能提案顯示: Java 平臺將引入虛擬執行緒特性。虛擬執行緒是輕量級執行緒,可顯著地減少編寫、維護和觀察高吞吐量並行應用程式的工作量。
Java 開發人員一直依賴執行緒作為並行伺服器應用程式的構建塊,每個方法中的語句都在一個執行緒內執行,每個執行緒提供一個堆疊來儲存區域性變數和協調方法呼叫,以及報錯時的上下文捕獲。執行緒是 Java 的並行單元,也是 Java 工具的核心基礎:偵錯程式逐步執行執行緒方法中的語句,分析器則視覺化多個執行緒的行為。
目前,JDK 將其平臺執行緒實現為作業系統 (OS) 執行緒的包裝器,JDK 中每個範例都是一個平臺執行緒,平臺執行緒在底層作業系統執行緒上執行 Java 程式碼 ,並在程式碼的整個生命週期內捕獲 OS 執行緒。平臺執行緒數受限於 OS 執行緒數,而 OS 執行緒的成本很高,不能佔用太多。因此,目前 JDK 的這種執行緒實現方法限制了其應用程式的吞吐量,使吞吐量遠低於硬體支援的水平。
關於虛擬執行緒
虛擬執行緒java.lang.Thread
是在底層作業系統執行緒(OS 執行緒)上執行 Java 程式碼,但在程式碼的整個生命週期內不捕獲 OS 執行緒的範例。這意味著許多虛擬執行緒可以在同一個 OS 執行緒上執行 Java 程式碼,從而有效地共用它。
虛擬執行緒是由 JDK 而不是作業系統提供的執行緒的輕量級實現,也是使用者模式執行緒的一種形式。使用者模式執行緒在 Java 的早期版本中被稱為,當時作業系統執行緒的概念還不夠成熟和普及, Java 的所有綠色執行緒都共用一個 OS 執行緒(M:1 排程),隨著執行緒概念的發展,綠色執行緒最終被現在的平臺執行緒超越,實現為 OS 執行緒的包裝器(1:1 排程),而最新引入的虛擬執行緒採用 M:N 排程,其中大量 (M) 虛擬執行緒被排程為在較少數量 (N) 的 OS 執行緒上執行。
更高的吞吐量
開發者可以選擇使用虛擬執行緒還是平臺執行緒,但虛擬執行緒在高吞吐量的伺服器應用程式中表現更好。比如下面這段休眠一秒鐘的程式碼就建立了大量的虛擬執行緒,程式首先獲得一個 ,它為每個提交的任務建立一個新的虛擬執行緒,然後提交 10000 個任務並等待所有任務完成::
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
IntStream.range(0, 10_000).forEach(i -> {
executor.submit(() -> {
Thread.sleep(Duration.ofSeconds(1));
return i;
});
});
} // executor.close() is called implicitly, and waits
現代硬體可以很容易地支援 10000 個虛擬執行緒同時執行這樣的程式碼。如果該程式使用為每個任務都建立一個新平臺執行緒的 ExecutorService,例如 Executors.newCachedThreadPool() , 那麼它將嘗試建立 10000 個平臺執行緒,也就意味著 10000 個 OS 執行緒,那麼這個程式在大多數作業系統上都會崩潰。又或者這個程式使用從池中獲取平臺執行緒的 ExecutorService,如 Executors.newFixedThreadPool(200),也好不到哪去。 ExecutorService 將建立 200 個平臺執行緒供這 10000 個任務共用,任務將按順序執行而不是同時執行,程式需要很長時間才能跑完。
對於上述程式來說,具有 200 個平臺執行緒的池只能實現每秒 200 個任務的吞吐量,而虛擬執行緒可以實現大約每秒 10000 個任務的吞吐量(在充分預熱之後)。此外,如果將範例程式中的 10000 更改為 1,000,000 ,則程式將提交 1,000,000 個任務,建立 1,000,000 個並行執行的虛擬執行緒,並且(在充分預熱後)達到大約 1,000,000 個任務/秒的吞吐量。
總而言之,虛擬執行緒不是更快的執行緒 —— 它們執行程式碼的速度並不比平臺執行緒快。它們的存在是為了提供規模(更高的吞吐量),而不是速度(更低的延遲)。
如何啟用虛擬執行緒?
目前虛擬執行緒在其他多執行緒語言中被廣泛使用(例如 Go 中的 goroutine 和 Erlang 中的程序,在 C++ 中也是一個穩定特性),但在 Java 中還是一個預覽 API,預設禁用。如要在 JDK XX 上嘗試該功能,則必須通過以下方法啟用預覽 API:
- 使用 javac --release XX --enable-preview Main.java 編譯程式,並使用 java --enable-preview Main 執行
- 使用原始碼啟動器時,使用 java --release XX --enable-preview Main.java 執行程式
- 使用 jshell 時,用 jshell --enable-preview 啟動
有關虛擬執行緒的更多資訊可在 OpenJDK 的 中檢視,目前該提案於 2021/11/15 創立,目前還處於 JEP 流程的第一階段,距離穩定版本還需要一段時間。