生活(huo)在數(shu)字化時(shi)代的(de)我們,在日常生活(huo)工作學習中或多或少(shao)遇到過(guo)這樣的(de)問(wen)題:雙(shuang)十(shi)一購物時(shi),提(ti)交訂單無法(fa)響(xiang)應(ying)或無法(fa)提(ti)交;查(cha)詢高考成績時(shi),網站打不開或打開了網站無法(fa)正(zheng)常登(deng)錄查(cha)分;春(chun)運高峰期(qi),搶(qiang)購火車(che)票時(shi),APP一直轉(zhuan)圈,卻搶(qiang)不到票。
“性(xing)(xing)能(neng)”是每一(yi)(yi)個程序員(yuan)在產品功(gong)能(neng)實(shi)現以后(hou)又愛又恨的(de)話題。一(yi)(yi)款上線的(de)產品,沒有(you)經過性(xing)(xing)能(neng)測試,猶(you)如一(yi)(yi)顆定(ding)時炸彈(dan),隨(sui)時會被引爆;有(you)的(de)性(xing)(xing)能(neng)問題又如調皮的(de)小孩,東躲西藏(zang),等到了(le)一(yi)(yi)定(ding)的(de)時間就爆炸了(le)。
而今在萬物互聯的物聯網時代,隨著(zhu)社會的進步,數(shu)字化城市的建立,性能會更加凸顯它的重(zhong)要性。面對各(ge)種各(ge)樣大(da)(da)的設(she)備連接,面對大(da)(da)量設(she)備的數(shu)據上(shang)報,物聯網系(xi)統(tong)無(wu)時無(wu)刻不(bu)在承受著(zhu)巨大(da)(da)的考驗與壓力。
虛(xu)擬線程(Virtual Threads)就猶如名字(zi)一樣(yang),并(bing)非(fei)傳統意(yi)義(yi)上(shang)的JAVA線程。
傳統(tong)意義上的(de)(de)JAVA線(xian)(xian)(xian)程(cheng)(cheng)(cheng)(以(yi)下(xia)稱為(wei)平(ping)臺(tai)(tai)(tai)(tai)線(xian)(xian)(xian)程(cheng)(cheng)(cheng))跟操(cao)作系統(tong)的(de)(de)內(nei)核線(xian)(xian)(xian)程(cheng)(cheng)(cheng)是一(yi)一(yi)映射的(de)(de)關(guan)系。而對于(yu)平(ping)臺(tai)(tai)(tai)(tai)線(xian)(xian)(xian)程(cheng)(cheng)(cheng)的(de)(de)創建和銷毀(hui)所(suo)(suo)帶(dai)來(lai)的(de)(de)開(kai)銷是非常大的(de)(de),所(suo)(suo)以(yi)JAVA采用線(xian)(xian)(xian)程(cheng)(cheng)(cheng)池的(de)(de)方式來(lai)維護(hu)平(ping)臺(tai)(tai)(tai)(tai)線(xian)(xian)(xian)程(cheng)(cheng)(cheng)而避免線(xian)(xian)(xian)程(cheng)(cheng)(cheng)的(de)(de)反復創建和銷毀(hui)。然(ran)而平(ping)臺(tai)(tai)(tai)(tai)線(xian)(xian)(xian)程(cheng)(cheng)(cheng)也(ye)(ye)會占用內(nei)存、CPU資(zi)源(yuan)(yuan),往往在(zai)CPU和網絡連(lian)接成(cheng)為(wei)系統(tong)瓶頸(jing)前,平(ping)臺(tai)(tai)(tai)(tai)線(xian)(xian)(xian)程(cheng)(cheng)(cheng)首當其沖的(de)(de)會成(cheng)為(wei)系統(tong)瓶頸(jing)。在(zai)單臺(tai)(tai)(tai)(tai)服(fu)(fu)務器硬件資(zi)源(yuan)(yuan)確定的(de)(de)情(qing)況下(xia),平(ping)臺(tai)(tai)(tai)(tai)線(xian)(xian)(xian)程(cheng)(cheng)(cheng)的(de)(de)數量(liang)同(tong)樣(yang)也(ye)(ye)會因為(wei)硬件資(zi)源(yuan)(yuan)而受(shou)到限(xian)制,也(ye)(ye)成(cheng)為(wei)單臺(tai)(tai)(tai)(tai)服(fu)(fu)務器吞吐量(liang)提升的(de)(de)主要障礙(ai)。

而虛擬線(xian)(xian)(xian)程(cheng)(cheng)(cheng)則是由JDK而非操作系統提(ti)供的(de)(de)一種(zhong)線(xian)(xian)(xian)程(cheng)(cheng)(cheng)輕量(liang)(liang)級(ji)實現(xian),它不(bu)依賴于平臺線(xian)(xian)(xian)程(cheng)(cheng)(cheng)的(de)(de)數量(liang)(liang),也(ye)不(bu)會增(zeng)加(jia)額外的(de)(de)上下文切(qie)換開(kai)銷(xiao),也(ye)不(bu)會在代(dai)碼(ma)的(de)(de)整個(ge)生命周期中阻(zu)塞系統線(xian)(xian)(xian)程(cheng)(cheng)(cheng)。整個(ge)虛擬線(xian)(xian)(xian)程(cheng)(cheng)(cheng)的(de)(de)維護是通過JVM進行(xing)管理,作為普通的(de)(de)JAVA對象存放(fang)在RAM中。那么意味著(zhu)若(ruo)干的(de)(de)虛擬線(xian)(xian)(xian)程(cheng)(cheng)(cheng)可以(yi)在同一個(ge)系統線(xian)(xian)(xian)程(cheng)(cheng)(cheng)上運行(xing)應用程(cheng)(cheng)(cheng)序的(de)(de)代(dai)碼(ma),只有在虛擬線(xian)(xian)(xian)程(cheng)(cheng)(cheng)執行(xing)的(de)(de)時候才(cai)會消(xiao)耗(hao)系統線(xian)(xian)(xian)程(cheng)(cheng)(cheng),在等待和(he)休眠時不(bu)會阻(zu)塞系統線(xian)(xian)(xian)程(cheng)(cheng)(cheng)。

虛(xu)擬(ni)線(xian)(xian)程(cheng)(cheng)是一種(zhong)非(fei)常廉價和豐富的(de)(de)線(xian)(xian)程(cheng)(cheng),可(ke)以說虛(xu)擬(ni)線(xian)(xian)程(cheng)(cheng)的(de)(de)數(shu)量(liang)是一種(zhong)近(jin)乎于無限(xian)多的(de)(de)線(xian)(xian)程(cheng)(cheng),它對硬(ying)件的(de)(de)利用率接近(jin)于最好,在相同硬(ying)件配置服務(wu)器(qi)的(de)(de)情況下,虛(xu)擬(ni)線(xian)(xian)程(cheng)(cheng)比使用平(ping)臺線(xian)(xian)程(cheng)(cheng)具備更高的(de)(de)并(bing)發性,從(cong)而(er)提升整個應用程(cheng)(cheng)序的(de)(de)吞吐量(liang)。如果(guo)說平(ping)臺線(xian)(xian)程(cheng)(cheng)和系統(tong)線(xian)(xian)程(cheng)(cheng)調度為1:1的(de)(de)方式(shi),虛(xu)擬(ni)線(xian)(xian)程(cheng)(cheng)則采用M:N的(de)(de)調度方式(shi),其中大(da)量(liang)的(de)(de)虛(xu)擬(ni)線(xian)(xian)程(cheng)(cheng)M在較少(shao)的(de)(de)系統(tong)線(xian)(xian)程(cheng)(cheng)N上(shang)運行。
那么虛擬(ni)線(xian)(xian)(xian)(xian)程(cheng)是如何被(bei)JVM調(diao)度呢?首先創建一(yi)個虛擬(ni)線(xian)(xian)(xian)(xian)程(cheng),此時JVM會將(jiang)(jiang)虛擬(ni)線(xian)(xian)(xian)(xian)程(cheng)裝載在平(ping)臺線(xian)(xian)(xian)(xian)程(cheng)上,平(ping)臺線(xian)(xian)(xian)(xian)程(cheng)則會去綁定一(yi)個系統線(xian)(xian)(xian)(xian)程(cheng)。JVM會使用調(diao)度程(cheng)序去使用調(diao)度線(xian)(xian)(xian)(xian)程(cheng)執行虛擬(ni)線(xian)(xian)(xian)(xian)程(cheng)中的任(ren)務(wu)(wu)。任(ren)務(wu)(wu)執行完成之后清(qing)空上下文變量,將(jiang)(jiang)調(diao)度線(xian)(xian)(xian)(xian)程(cheng)返還至調(diao)度程(cheng)序等待處(chu)理下一(yi)個任(ren)務(wu)(wu)。

虛擬線程(cheng)VS平臺線程(cheng)
虛擬(ni)線程的(de)使(shi)用其(qi)實非常簡單,跟平臺(tai)線程的(de)使(shi)用方式基(ji)本相同(tong),唯(wei)一不同(tong)的(de)是創(chuang)建虛擬(ni)線程時,需要調用newVirtualThreadPerTaskExecutor()來創(chuang)建虛擬(ni)線程。
以下我將三(san)種(zhong)(zhong)線程創建(jian)的(de)方式來模擬(ni)高并發IO,并打印系統線程數,得到(dao)三(san)種(zhong)(zhong)線程對處理10萬累加(jia)計(ji)數的(de)時長。
? 主程序:
主程序采用一個定時任務,每一秒(miao)打(da)印一次所消耗(hao)的(de)系統(tong)線(xian)程數。

第一(yi)種方式,無限(xian)制的(de)使(shi)用普通線(xian)程(平臺線(xian)程),不需要(yao)考慮OOM的(de)情況(kuang):

? 三次運行結果:



普(pu)通線程(平臺線程)耗時(shi)(三次): 9584 ms 、10189ms、9586ms
普通(tong)線程(cheng)(平(ping)臺線程(cheng))count計數為: 100000
初始占用系統線程(cheng)數:9;峰值占用系統線程(cheng)線程(cheng)數:20027、19137、19140
第二種方(fang)式,使(shi)用(yong)線(xian)程池模式創建普通線(xian)程(平臺(tai)線(xian)程),考慮OOM的情況,線(xian)程池中(zhong)創建1000普通線(xian)程:

? 三次(ci)運行(xing)結果(由(you)于運行(xing)時間過長,無法完(wan)整(zheng)截圖起始線(xian)程數):



線程(cheng)(cheng)池模式(shi)1000普通(tong)線程(cheng)(cheng)(平臺線程(cheng)(cheng))耗(hao)時(三次(ci)): 100165ms 、100146ms、100159ms
線程(cheng)池(chi)模式(shi)1000普通線程(cheng)(平臺線程(cheng))count計數為: 100000
初始占用(yong)系統(tong)線程數(shu):9;峰值占用(yong)系統(tong)線程線程數(shu):1009、1009、1009
第三種方式,使用虛擬線(xian)程(cheng)模(mo)式,創建10萬個虛擬線(xian)程(cheng):
? 三次運行結果:




虛擬(ni)線程耗時(三次): 2290ms、2523ms、2412ms
虛擬線程(cheng)(平臺(tai)線程(cheng))count計數(shu)為(wei): 100000
初始占(zhan)用(yong)系統線程(cheng)數:9;峰(feng)值占(zhan)用(yong)系統線程(cheng)線程(cheng)數:16
由(you)于JVM對(dui)系(xi)(xi)統線程的(de)釋(shi)放機制,峰值占用系(xi)(xi)統線程數會逐(zhu)漸從16降至(zhi)9,由(you)于釋(shi)放需(xu)要一定時間,沒對(dui)釋(shi)放系(xi)(xi)統線程進行(xing)完整截(jie)圖。


由上表(biao)可見(jian),線(xian)(xian)(xian)程(cheng)(cheng)池(chi)模式處(chu)理(li)10萬累(lei)加(jia)并(bing)(bing)發處(chu)理(li)的(de)耗時(shi)是虛擬線(xian)(xian)(xian)程(cheng)(cheng)耗時(shi)的(de)50倍;在不(bu)(bu)(bu)考慮服(fu)務(wu)內存OOM的(de)情況下,普通線(xian)(xian)(xian)程(cheng)(cheng)模式占用(yong)(yong)了(le)大量系統線(xian)(xian)(xian)程(cheng)(cheng)處(chu)理(li)10萬累(lei)加(jia)并(bing)(bing)發耗時(shi)也是虛擬線(xian)(xian)(xian)程(cheng)(cheng)的(de)5倍。虛擬線(xian)(xian)(xian)程(cheng)(cheng)只(zhi)占用(yong)(yong)了(le)7個系統線(xian)(xian)(xian)程(cheng)(cheng),來(lai)處(chu)理(li)10萬累(lei)加(jia)并(bing)(bing)發,這(zhe)已經不(bu)(bu)(bu)能用(yong)(yong)并(bing)(bing)發的(de)巨大的(de)性(xing)能提升來(lai)描(miao)述,而是并(bing)(bing)發怪獸(shou),性(xing)能革命(ming)!但(dan)是虛擬線(xian)(xian)(xian)程(cheng)(cheng)的(de)運行速度并(bing)(bing)不(bu)(bu)(bu)比(bi)平臺線(xian)(xian)(xian)程(cheng)(cheng)快(kuai),所以(yi)不(bu)(bu)(bu)能用(yong)(yong)來(lai)降低延遲。
那么什么時候可以使用虛擬線程?
應用(yong)系統有大量(liang)的并(bing)發(fa)(fa)任(ren)務(wu)(超(chao)過(guo)幾千個(ge)并(bing)發(fa)(fa)任(ren)務(wu)),這些任(ren)務(wu)也需要大量(liang)的時間等待(dai);
IO密集型場景,工作負載不受(shou)CPU限(xian)制。
如何改造當前的線程池?
直接(jie)用虛擬線程(cheng)代替線程(cheng)池,如果代碼中使用CompletableFuture,則直接(jie)將異步執行任務(wu)線程(cheng)池替換為:Executors.newVirtualThreadPerTaskExecutor().
虛擬線程非常輕量化(hua),不需要創建(jian)池(chi),直接創建(jian)虛擬線程即(ji)可;
synchronized更改為ReentrantLock減(jian)少固定到平臺線(xian)程的虛擬(ni)線(xian)程;
虛(xu)擬線(xian)程中ThreadLocal使用方式和平臺(tai)線(xian)程一致(zhi),但創建了(le)大(da)量的(de)虛(xu)擬線(xian)程,每個虛(xu)擬線(xian)程中均有ThreadLocal實例及其(qi)引用的(de)數據,則(ze)會(hui)對內存(cun)帶來很(hen)大(da)的(de)負擔。
總結
在萬物(wu)互聯(lian)的(de)今天,物(wu)聯(lian)網平臺日(ri)益(yi)增(zeng)長的(de)設備(bei)連接數和(he)龐(pang)大的(de)并發量已(yi)經不是我(wo)們(men)能(neng)忽視的(de)問(wen)題(ti),JDK19中的(de)性能(neng)怪(guai)獸--虛擬線(xian)程(cheng)(cheng)給(gei)我(wo)們(men)帶來(lai)(lai)了一個嶄新的(de)方(fang)向來(lai)(lai)解決物(wu)聯(lian)網平臺并發量的(de)問(wen)題(ti)。虛擬線(xian)程(cheng)(cheng)中還有很多可以深挖和(he)學(xue)習與借鑒的(de)前沿技(ji)術和(he)設計思想,這需要(yao)我(wo)們(men)不斷的(de)探究和(he)實踐來(lai)(lai)提升我(wo)們(men)的(de)OneNET平臺,以應(ying)對未來(lai)(lai)無(wu)限(xian)的(de)機遇與挑戰。