Java終于開始引入虛擬線程(協程)了
高并發、多線程一直是Java編程中的難點,也是面試題中的要點。Java開發者也一直在嘗試使用多線程來解決應用服務器的并發問題。但是多線程并不容易,為此一個新的技術出現了,這就是虛擬線程。
傳統多線程的痛點
但是編寫多線程代碼是非常不容易的,難以控制的執行順序,共享變量的線程安全性,異常可觀察性等等都是多線程編程的難點。
如果每個請求在請求的持續時間內都在一個線程中處理,那么為了提高應用程序的吞吐量,線程的數量必須隨著吞吐量的增長而增長。不幸的是線程是稀缺資源,創建一個線程的代價是昂貴的,即使引入了池化技術也無法降低新線程的創建成本,而且 JDK 當前的線程實現將應用程序的吞吐量限制在遠低于硬件可以支持的水平。
為此很多開發人員轉向了異步編程,例如CompletableFuture或者現在正熱的反應式框架。但是這些技術要么擺脫不了“回調地獄”,要么缺乏可觀測性。
解決這些痛點、增強Java平臺的和諧,實現每個請求使用獨立線程(thread-per-request style)這種風格成為必要之舉。能否實現一種“成本低廉”的虛擬線程來映射到系統線程以減少對系統線程的直接操作呢?思路應該是沒問題的!于是Java社區發起了關于虛擬線程的JEP 425[1]提案。
虛擬線程
虛擬線程(virtual threads)應該非常廉價而且可以無需擔心系統硬件資源被大量創建,并且不應該被池化。應該為每個應用程序任務創建一個新的虛擬線程。因此,大多數虛擬線程將是短暫的并且具有淺層調用堆棧,只執行單個 HTTP 客戶端調用或單個 JDBC 查詢。與之對應的平臺線程( Platform Threads,也就是現在傳統的JVM線程 )是重量級且昂貴的,因此通常必須被池化。它們往往壽命長,有很深的調用堆棧,并且在許多任務之間共享。
總而言之,虛擬線程保留了與 Java 平臺的設計相協調的、可靠的每請求線程樣式,同時優化了硬件的利用。使用虛擬線程不需要學習新概念,甚至需要改掉現在操作多線程的習慣,使用更加容易上手的API、兼容以前的多線程設計、并且絲毫不會影響代碼的拓展性。
平臺線程和虛擬線程的不同
為了更好理解這一個設計,草案對這兩種線程進行了比較。
現在的線程現在每個java.lang.Thread都是一個平臺線程,平臺線程在底層操作系統線程上運行 Java 代碼,并在代碼的整個生命周期內捕獲操作系統線程。平臺線程數受限于 OS 線程數。
平臺線程
并不會因為加入虛擬線程而退出歷史舞臺。
未來的虛擬線程
虛擬線程是由 JDK 而不是操作系統提供的線程的輕量級實現。它們是用戶模式線程的一種形式,在其他多線程語言中已經成功(比如Golang中的協程和Erlang中的進程)。虛擬線程采用 M:N 調度,其中大量 (M) 虛擬線程被調度為在較少數量 (N) 的 OS 線程上運行。JDK 的虛擬線程調度程序是一種ForkJoinPool工作竊取的機制,以 FIFO 模式運行。
我們可以很隨意地創建10000個虛擬線程:
// 預覽代碼
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
IntStream.range(0, 10_000).forEach(i -> {
executor.submit(() -> {
Thread.sleep(Duration.ofSeconds(1));
return i;
});
});
}
無需擔心硬件資源是否扛得住,反過來如果你使用Executors.newCachedThreadPool()創建10000個平臺線程,在大多數操作系統上很容易因資源不足而崩潰。
為吞吐量而設計
但是這里依然要說明一點,虛擬線程并非為了提升執行速度而設計。它并不比平臺線程速度快,它們的存在是為了提供規模(更高的吞吐量),而不是速度(更低的延遲)。它們的數量可能比平臺線程多得多,因此根據利特爾定律,它們可以實現更高吞吐量所需的更高并發性。
換句話說,虛擬線程可以顯著提高應用程序吞吐量
- 并發任務的數量很高(超過幾千個),并且
- 工作負載不受 CPU 限制,因為在這種情況下,擁有比處理器內核多得多的線程并不能提高吞吐量。
虛擬線程有助于提高傳統服務器應用程序的吞吐量,正是因為此類應用程序包含大量并發任務,這些任務花費大量的時間等待。
增強可觀測性
編寫清晰的代碼并不是全部。對正在運行的程序狀態的清晰表示對于故障排除、維護和優化也很重要,JDK 長期以來一直提供調試、分析和監視線程的機制。在虛擬線程中也會增強代碼的可觀測性,讓開發人員更好地調試代碼。
新的線程API
為此增加了新的線程API設計,目前放出的部分如下:
- Thread.Builder 線程構建器。
- ThreadFactory 能批量構建相同特性的線程工廠。
- Thread.ofVirtual() 創建一個虛擬線程。
- Thread.ofPlatform() 創建一個平臺線程。
- Thread.startVirtualThread(Runnable) 一種創建然后啟動虛擬線程的便捷方式。
- Thread.isVirtual() 測試線程是否是虛擬線程。
還有很多就不一一演示了,有興趣的自行去看JEP425。
總結
協程在Java社區已經呼喚了很久了,現在終于有了實質性的動作,這是一個非常重要的特性。不過這個功能涉及的東西還是很多的,包括平臺線程的兼容性、對ThreadLocal的一些影響、對JUC的影響。可能需要多次預覽才能最終落地,不過這已經是很大的進步了,起碼距離實裝已經不遠了,胖哥可能趕不上那個時候了,不過很多年輕的同學應該能夠趕上。




























