生活在數(shu)字化時代的(de)我(wo)們(men),在日(ri)常生活工作學(xue)習中(zhong)或(huo)(huo)多或(huo)(huo)少(shao)遇到(dao)過(guo)這樣(yang)的(de)問題:雙十一購(gou)物時,提(ti)交訂(ding)單無法響應或(huo)(huo)無法提(ti)交;查詢高考(kao)成績時,網站打不(bu)(bu)開或(huo)(huo)打開了(le)網站無法正常登錄查分;春運高峰期,搶購(gou)火車票時,APP一直轉圈,卻搶不(bu)(bu)到(dao)票。
“性能”是每一個程序員在產品功能實現以后又愛又恨的(de)話題。一款(kuan)上(shang)線的(de)產品,沒有經過(guo)性能測試,猶(you)如一顆定(ding)(ding)時炸(zha)彈,隨時會被引爆;有的(de)性能問題又如調皮的(de)小孩,東躲西藏,等到了一定(ding)(ding)的(de)時間就爆炸(zha)了。
而今在萬物互聯的物聯網時代,隨著(zhu)(zhu)社(she)會的(de)(de)進步,數(shu)字(zi)化城市的(de)(de)建立,性(xing)能會更加(jia)凸顯它的(de)(de)重要(yao)性(xing)。面對各種各樣大(da)的(de)(de)設備連接,面對大(da)量設備的(de)(de)數(shu)據(ju)上報(bao),物聯網系(xi)統無(wu)時無(wu)刻不在承受(shou)著(zhu)(zhu)巨(ju)大(da)的(de)(de)考(kao)驗與壓力。
虛擬(ni)線(xian)(xian)程(Virtual Threads)就猶如名(ming)字一樣,并非傳統(tong)意(yi)義上的JAVA線(xian)(xian)程。
傳統(tong)(tong)意義(yi)上的(de)(de)(de)JAVA線(xian)程(cheng)(cheng)(以下稱為(wei)(wei)平(ping)(ping)臺(tai)線(xian)程(cheng)(cheng))跟操作系(xi)(xi)統(tong)(tong)的(de)(de)(de)內(nei)核線(xian)程(cheng)(cheng)是(shi)一一映(ying)射的(de)(de)(de)關系(xi)(xi)。而對于平(ping)(ping)臺(tai)線(xian)程(cheng)(cheng)的(de)(de)(de)創建和銷毀所帶(dai)來(lai)的(de)(de)(de)開銷是(shi)非常大的(de)(de)(de),所以JAVA采用線(xian)程(cheng)(cheng)池的(de)(de)(de)方式來(lai)維護平(ping)(ping)臺(tai)線(xian)程(cheng)(cheng)而避免線(xian)程(cheng)(cheng)的(de)(de)(de)反復創建和銷毀。然而平(ping)(ping)臺(tai)線(xian)程(cheng)(cheng)也(ye)會占用內(nei)存、CPU資源(yuan),往往在CPU和網絡連(lian)接成為(wei)(wei)系(xi)(xi)統(tong)(tong)瓶(ping)頸前,平(ping)(ping)臺(tai)線(xian)程(cheng)(cheng)首(shou)當其沖的(de)(de)(de)會成為(wei)(wei)系(xi)(xi)統(tong)(tong)瓶(ping)頸。在單臺(tai)服務器硬件資源(yuan)確定的(de)(de)(de)情況下,平(ping)(ping)臺(tai)線(xian)程(cheng)(cheng)的(de)(de)(de)數(shu)量同樣(yang)也(ye)會因為(wei)(wei)硬件資源(yuan)而受到限制,也(ye)成為(wei)(wei)單臺(tai)服務器吞吐(tu)量提升的(de)(de)(de)主要障礙(ai)。
而虛擬(ni)線(xian)程則是由(you)JDK而非操作(zuo)(zuo)系(xi)(xi)統提供的(de)一種線(xian)程輕量級實現,它(ta)不依賴于平臺線(xian)程的(de)數量,也(ye)不會增加額外的(de)上下(xia)文切換開(kai)銷,也(ye)不會在(zai)(zai)代(dai)碼的(de)整個生(sheng)命周(zhou)期中(zhong)阻塞(sai)系(xi)(xi)統線(xian)程。整個虛擬(ni)線(xian)程的(de)維護是通過JVM進行(xing)(xing)管理,作(zuo)(zuo)為(wei)普通的(de)JAVA對象(xiang)存(cun)放在(zai)(zai)RAM中(zhong)。那么意味著若干的(de)虛擬(ni)線(xian)程可以在(zai)(zai)同一個系(xi)(xi)統線(xian)程上運行(xing)(xing)應(ying)用程序的(de)代(dai)碼,只有(you)在(zai)(zai)虛擬(ni)線(xian)程執行(xing)(xing)的(de)時(shi)候才會消耗系(xi)(xi)統線(xian)程,在(zai)(zai)等待和休眠(mian)時(shi)不會阻塞(sai)系(xi)(xi)統線(xian)程。
虛(xu)擬(ni)(ni)線(xian)(xian)(xian)(xian)程(cheng)(cheng)(cheng)是一種非(fei)常廉(lian)價和(he)豐富(fu)的(de)線(xian)(xian)(xian)(xian)程(cheng)(cheng)(cheng),可(ke)以說虛(xu)擬(ni)(ni)線(xian)(xian)(xian)(xian)程(cheng)(cheng)(cheng)的(de)數(shu)量(liang)(liang)是一種近乎(hu)于(yu)無限多的(de)線(xian)(xian)(xian)(xian)程(cheng)(cheng)(cheng),它對硬件的(de)利用率(lv)接(jie)近于(yu)最好,在相同硬件配置(zhi)服(fu)務器的(de)情況下,虛(xu)擬(ni)(ni)線(xian)(xian)(xian)(xian)程(cheng)(cheng)(cheng)比使用平(ping)臺(tai)線(xian)(xian)(xian)(xian)程(cheng)(cheng)(cheng)具備更高的(de)并發性,從而(er)提升(sheng)整個(ge)應用程(cheng)(cheng)(cheng)序的(de)吞(tun)吐量(liang)(liang)。如果(guo)說平(ping)臺(tai)線(xian)(xian)(xian)(xian)程(cheng)(cheng)(cheng)和(he)系(xi)統(tong)線(xian)(xian)(xian)(xian)程(cheng)(cheng)(cheng)調(diao)度為1:1的(de)方式,虛(xu)擬(ni)(ni)線(xian)(xian)(xian)(xian)程(cheng)(cheng)(cheng)則采用M:N的(de)調(diao)度方式,其中大(da)量(liang)(liang)的(de)虛(xu)擬(ni)(ni)線(xian)(xian)(xian)(xian)程(cheng)(cheng)(cheng)M在較少(shao)的(de)系(xi)統(tong)線(xian)(xian)(xian)(xian)程(cheng)(cheng)(cheng)N上運行。
那(nei)么(me)虛擬(ni)(ni)線(xian)程(cheng)是如何被JVM調(diao)度(du)(du)(du)呢?首(shou)先創(chuang)建(jian)一個虛擬(ni)(ni)線(xian)程(cheng),此時JVM會將(jiang)虛擬(ni)(ni)線(xian)程(cheng)裝載在平(ping)臺線(xian)程(cheng)上,平(ping)臺線(xian)程(cheng)則(ze)會去綁定一個系統(tong)線(xian)程(cheng)。JVM會使(shi)用調(diao)度(du)(du)(du)程(cheng)序(xu)去使(shi)用調(diao)度(du)(du)(du)線(xian)程(cheng)執(zhi)行虛擬(ni)(ni)線(xian)程(cheng)中的任務。任務執(zhi)行完成之(zhi)后(hou)清(qing)空上下文變量,將(jiang)調(diao)度(du)(du)(du)線(xian)程(cheng)返還至調(diao)度(du)(du)(du)程(cheng)序(xu)等待處理下一個任務。
虛擬線(xian)程VS平(ping)臺線(xian)程
虛(xu)擬(ni)線程的使用其實(shi)非(fei)常簡單,跟平臺線程的使用方式基本相同,唯一不同的是創建虛(xu)擬(ni)線程時,需要調用newVirtualThreadPerTaskExecutor()來創建虛(xu)擬(ni)線程。
以下我將(jiang)三種(zhong)線(xian)(xian)程創建的方式來模擬高并發IO,并打印系統線(xian)(xian)程數,得到三種(zhong)線(xian)(xian)程對處理10萬累加計數的時(shi)長。
? 主程序:
主程序(xu)采用一(yi)個定時任務,每一(yi)秒(miao)打印一(yi)次所消耗(hao)的系統(tong)線程數。
第一種方式(shi),無限制的使用普通(tong)線(xian)程(cheng)(平臺線(xian)程(cheng)),不需要考慮OOM的情(qing)況:
? 三次運行結果:
普(pu)通線(xian)程(平臺線(xian)程)耗時(三次): 9584 ms 、10189ms、9586ms
普通線(xian)程(cheng)(平臺線(xian)程(cheng))count計數為: 100000
初始占(zhan)用(yong)系統(tong)線(xian)(xian)程(cheng)數:9;峰值占(zhan)用(yong)系統(tong)線(xian)(xian)程(cheng)線(xian)(xian)程(cheng)數:20027、19137、19140
第二種方式,使用線程(cheng)池(chi)模(mo)式創建普(pu)(pu)通線程(cheng)(平臺線程(cheng)),考(kao)慮OOM的情況(kuang),線程(cheng)池(chi)中創建1000普(pu)(pu)通線程(cheng):
? 三次運行結(jie)果(guo)(由于運行時間過長(chang),無(wu)法完整(zheng)截圖起始線程數):
線程池模式1000普通線程(平(ping)臺線程)耗時(三次): 100165ms 、100146ms、100159ms
線(xian)(xian)程(cheng)池模式1000普通線(xian)(xian)程(cheng)(平(ping)臺線(xian)(xian)程(cheng))count計(ji)數為(wei): 100000
初始占用(yong)系(xi)統(tong)線程數:9;峰(feng)值占用(yong)系(xi)統(tong)線程線程數:1009、1009、1009
第三種(zhong)方式,使用(yong)虛(xu)擬(ni)線(xian)程模式,創建10萬個虛(xu)擬(ni)線(xian)程:
? 三次運行結果:
虛擬線程(cheng)耗時(三次): 2290ms、2523ms、2412ms
虛擬線程(cheng)(平臺線程(cheng))count計數為: 100000
初始占(zhan)用系統(tong)線程數:9;峰值(zhi)占(zhan)用系統(tong)線程線程數:16
由于(yu)JVM對系統線(xian)程(cheng)的釋放(fang)(fang)機(ji)制,峰值(zhi)占用系統線(xian)程(cheng)數會逐漸從16降至9,由于(yu)釋放(fang)(fang)需要一定時間,沒(mei)對釋放(fang)(fang)系統線(xian)程(cheng)進行完整截(jie)圖。
由(you)上表可見,線(xian)程池(chi)模式處理10萬(wan)累加(jia)(jia)并發(fa)(fa)處理的(de)耗(hao)時(shi)是(shi)(shi)虛(xu)(xu)(xu)擬線(xian)程耗(hao)時(shi)的(de)50倍;在(zai)不(bu)考(kao)慮(lv)服務內存OOM的(de)情況下,普通線(xian)程模式占(zhan)用了大量系統線(xian)程處理10萬(wan)累加(jia)(jia)并發(fa)(fa)耗(hao)時(shi)也是(shi)(shi)虛(xu)(xu)(xu)擬線(xian)程的(de)5倍。虛(xu)(xu)(xu)擬線(xian)程只占(zhan)用了7個(ge)系統線(xian)程,來(lai)處理10萬(wan)累加(jia)(jia)并發(fa)(fa),這已(yi)經不(bu)能(neng)用并發(fa)(fa)的(de)巨(ju)大的(de)性(xing)能(neng)提升來(lai)描述(shu),而是(shi)(shi)并發(fa)(fa)怪獸,性(xing)能(neng)革命!但(dan)是(shi)(shi)虛(xu)(xu)(xu)擬線(xian)程的(de)運行速度并不(bu)比平臺線(xian)程快,所以不(bu)能(neng)用來(lai)降低延(yan)遲(chi)。
那么什么時候可以使用虛擬線程?
應用系統有大量(liang)的并發(fa)任(ren)務(超過幾(ji)千個并發(fa)任(ren)務),這些任(ren)務也需要大量(liang)的時間(jian)等(deng)待;
IO密集型場景(jing),工作負載不受CPU限制。
如何改造當前的線程池?
直(zhi)接(jie)(jie)用虛擬線(xian)(xian)程代替(ti)線(xian)(xian)程池,如果代碼中使用CompletableFuture,則直(zhi)接(jie)(jie)將異步執行任務線(xian)(xian)程池替(ti)換為(wei):Executors.newVirtualThreadPerTaskExecutor().
虛擬線程(cheng)非常輕(qing)量化(hua),不需要(yao)創(chuang)建(jian)(jian)池,直接創(chuang)建(jian)(jian)虛擬線程(cheng)即可;
synchronized更改為ReentrantLock減少固定到平(ping)臺線程(cheng)的虛擬線程(cheng);
虛(xu)(xu)擬線(xian)程中ThreadLocal使(shi)用方式和平臺線(xian)程一致(zhi),但(dan)創建(jian)了大量的虛(xu)(xu)擬線(xian)程,每個虛(xu)(xu)擬線(xian)程中均有ThreadLocal實例及其引用的數據,則會對內存(cun)帶來很大的負擔(dan)。
總結
在萬物(wu)互(hu)聯(lian)(lian)的(de)(de)今天,物(wu)聯(lian)(lian)網(wang)平臺(tai)日益增長的(de)(de)設(she)備(bei)連接數和龐(pang)大(da)的(de)(de)并(bing)發(fa)(fa)量已經不(bu)是我(wo)們(men)能忽視(shi)的(de)(de)問題(ti),JDK19中(zhong)的(de)(de)性能怪獸--虛(xu)擬線程給我(wo)們(men)帶來了一個嶄新(xin)的(de)(de)方向來解決物(wu)聯(lian)(lian)網(wang)平臺(tai)并(bing)發(fa)(fa)量的(de)(de)問題(ti)。虛(xu)擬線程中(zhong)還(huan)有很(hen)多可以深挖和學習(xi)與借鑒的(de)(de)前(qian)沿技術和設(she)計思(si)想,這需要(yao)我(wo)們(men)不(bu)斷的(de)(de)探(tan)究和實踐(jian)來提升(sheng)我(wo)們(men)的(de)(de)OneNET平臺(tai),以應(ying)對未來無限的(de)(de)機(ji)遇與挑(tiao)戰(zhan)。