虛擬線程java.lang.Thread是在底層操作系統線程(OS 線程)上運行 Java 代碼,但在代碼的整個生命周期內不捕獲 OS 線程的實例。這意味著許多虛擬線程可以在同一個 OS 線程上運行 Java 代碼,從而有效地共享它。
虛擬線程是由 JDK 而不是操作系統提供的線程的輕量級實現,也是用戶模式線程的一種形式。用戶模式線程在 Java 的早期版本中被稱為“綠色線程”,當時操作系統線程的概念還不夠成熟和普及, Java 的所有綠色線程都共享一個 OS 線程(M:1 調度),隨著線程概念的發展,綠色線程最終被現在的平臺線程超越,實現為 OS 線程的包裝器(1:1 調度),而最新引入的虛擬線程采用 M:N 調度,其中大量 (M) 虛擬線程被調度為在較少數量 (N) 的 OS 線程上運行。
開發者可以選擇使用虛擬線程還是平臺線程,但虛擬線程在高吞吐量的服務器應用程序中表現更好。比如下面這段休眠一秒鐘的代碼就創建了大量的虛擬線程,程序首先獲得一個 ExecutorService,它為每個提交的任務創建一個新的虛擬線程,然后提交 10000 個任務并等待所有任務完成:
現代硬件可以很容易地支持 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 中的協程 和 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 的 JDK Issue-8277131 中查看,目前該提案于 2021/11/15 創立,目前還處于 JEP 流程的第一階段,距離穩定版本還需要一段時間。