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