欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

淺析Java中的虛擬線程

 更新時(shí)間:2023年10月27日 10:44:58   作者:翟睿  
在本篇文章中,小編將帶大家深入了解Java虛擬線程的原理、如何使用、使用的注意事項(xiàng)以及其他相似技術(shù)的差別,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下

在最近發(fā)布的JDK 21 LTS版本中,加入了許多新特性。其中對(duì)我們開發(fā)人員影響最大的應(yīng)該是分代ZGC以及Java的虛擬線程。在本篇文章中,我將帶大家深入了解Java虛擬線程的原理、如何使用、使用的注意事項(xiàng)以及其他相似技術(shù)的差別。

什么是虛擬線程

首先,我們需要了解什么是虛擬線程。

在平時(shí)的開發(fā)過(guò)程中,我們所使用的多線程往往意味著平臺(tái)線程。平臺(tái)線程代表著JVM直接與操作系統(tǒng)交互,創(chuàng)建了一個(gè)一個(gè)的線程,并且在JVM中還要為這個(gè)線程單獨(dú)開辟內(nèi)存使用。一般在JVM中創(chuàng)建一個(gè)平臺(tái)線程,開銷大約在1M左右。為了避免創(chuàng)建線程和銷毀線程帶來(lái)的巨大開銷,我們通常選擇使用池化技術(shù)來(lái)維護(hù)一些活躍的線程。

此外,線程的數(shù)量也需要嚴(yán)格控制。如果在一個(gè)線程池中維護(hù)成千上百個(gè)線程,往往效率并不盡如人意。因?yàn)榫€程在切換時(shí)涉及到CPU上下文的切換,如果線程數(shù)過(guò)多,反而會(huì)降低執(zhí)行效率。因此,如何控制線程池的大小也是考驗(yàn)工程師經(jīng)驗(yàn)的難點(diǎn)。

平臺(tái)線程與系統(tǒng)線程關(guān)系如下

為了解決這個(gè)問(wèn)題,虛擬線程應(yīng)運(yùn)而生,虛擬線程并不是Java的首創(chuàng),它在很多其他語(yǔ)言中被稱為協(xié)程、纖程、綠色線程、用戶態(tài)線程等,虛擬線程相對(duì)平臺(tái)線程,并不直接與操作系統(tǒng)交互,虛擬線程的數(shù)據(jù)是維護(hù)在堆內(nèi)存中,由JVM創(chuàng)建的平臺(tái)線程來(lái)持有,由平臺(tái)線程來(lái)決定什么時(shí)候來(lái)切換虛擬線程,大概圖如下

雖然圖中只畫了幾個(gè)虛擬線程,但是在實(shí)際使用中,我們可以創(chuàng)建成百上千的虛擬線程而不用擔(dān)心資源消耗的問(wèn)題

首先原因在于虛擬線程的開銷極其廉價(jià),一個(gè)虛擬線程可能才使用幾百字節(jié),所以幾遍創(chuàng)建成百上千也不會(huì)消耗太多內(nèi)存資源

其次雖然我們有極多的虛擬線程,但是實(shí)際上執(zhí)行線程依舊只有幾個(gè)平臺(tái)線程,所以在線程使用中不會(huì)由于CPU上下文的切換導(dǎo)致的額外開銷。

使用

接下來(lái)我們用代碼來(lái)實(shí)踐一下虛擬線程,JDK的工程師為了方便我們快速進(jìn)行虛擬線程的升級(jí),可以使用Thread來(lái)快速創(chuàng)建虛擬線程

Thread vt = Thread.startVirtualThread(() -> {
    Thread.sleep(1000);
});

這種方法創(chuàng)建的虛擬線程會(huì)立刻啟動(dòng),那么如果我們想創(chuàng)建一個(gè)需要手動(dòng)啟動(dòng)的虛擬線程,可以參考如下的方式

// 創(chuàng)建VirtualThread
Thread.ofVirtual().unstarted(() -> {
    Thread.sleep(1000);
});
// 運(yùn)行
vt.start();

還可以創(chuàng)建虛擬線程的工廠來(lái)使用

// 創(chuàng)建ThreadFactory:
ThreadFactory tf = Thread.ofVirtual().factory();
// 創(chuàng)建VirtualThread:
Thread vt = tf.newThread(() -> {
    Thread.sleep(1000);
});
// 運(yùn)行:
vt.start();

我們將線程的上下文信息打印出來(lái)看看

VirtualThread[#21]/runnable@ForkJoinPool-1-worker-1

從這個(gè)打印內(nèi)容可以大概看出,虛擬線程底層采用了ForkJoinPool來(lái)維護(hù)平臺(tái)線程,而后面的worker-1則代表具體的平臺(tái)線程的名稱,前面的#21代表當(dāng)前虛擬線程的數(shù)量,那么可能有同學(xué)就會(huì)問(wèn)了 那如果我全局有成千上百個(gè)虛擬線程,我怎么知道是哪塊業(yè)務(wù)的線程除了問(wèn)題呢。

我們可以對(duì)線程工廠進(jìn)行命名,使用該線程工廠命名后,在線程上下文中就會(huì)打印對(duì)應(yīng)虛擬線程工廠的信息。

ThreadFactory factory = Thread.ofVirtual().name("myVirtual").factory();
        try(ExecutorService executor = Executors.newThreadPerTaskExecutor(factory)) {
            for (int i=0; i<100000; i++) {
                // 也可以直接傳入Runnable或Callable:
                executor.submit(() -> {
                    System.out.println(Thread.currentThread());
                    Thread.sleep(1000);
                    return true;
                });
            }
        }

VirtualThread[#23,myVirtual]/runnable@ForkJoinPool-1-worker-2
VirtualThread[#275,myVirtual]/runnable@ForkJoinPool-1-worker-7
VirtualThread[#282,myVirtual]/runnable@ForkJoinPool-1-worker-5
VirtualThread[#285,myVirtual]/runnable@ForkJoinPool-1-worker-4
VirtualThread[#288,myVirtual]/runnable@ForkJoinPool-1-worker-3
VirtualThread[#234,myVirtual]/runnable@ForkJoinPool-1-worker-10

從日志中不難看出,我們已經(jīng)成功在線程上下文標(biāo)記了這一塊虛擬線程屬于哪個(gè)工廠,并且通過(guò)信息后綴中的worker-10可以看出底層的ForkJoinPool已經(jīng)創(chuàng)建了10個(gè)平臺(tái)線程來(lái)操作虛擬線程,因?yàn)槲疫@臺(tái)電腦是10核的,所以底層的線程池會(huì)創(chuàng)建與核心數(shù)相等的線程來(lái)操作虛擬線程,在我的實(shí)踐中發(fā)現(xiàn),無(wú)論你創(chuàng)建多少調(diào)度器,所有底層的虛擬線程都是由同一個(gè)平臺(tái)線程池來(lái)操控的。

而且在示例代碼中,我創(chuàng)建了十萬(wàn)個(gè)虛擬線程,如果我是用的是平臺(tái)線程,且不說(shuō)執(zhí)行效率,但是內(nèi)存分配就已經(jīng)使項(xiàng)目OOM了。

注意事項(xiàng)

ThreadLocal支持

有同學(xué)可能會(huì)問(wèn),在虛擬線程中能否使用ThreadLocal對(duì)象呢?官方給出的答案是,能夠使用但不建議。在過(guò)去,我們?cè)诰S護(hù)線程池時(shí),線程的數(shù)量是固定的且相對(duì)較少。我們通過(guò)手動(dòng)清理的方式來(lái)重用ThreadLocal對(duì)象。然而,在使用虛擬線程后,我們不再關(guān)注具體的線程數(shù),這導(dǎo)致ThreadLocal對(duì)象的數(shù)量無(wú)法控制,從而占用了額外的內(nèi)存。需要注意的是,虛擬線程與平臺(tái)線程不共享同一個(gè)ThreadLocal。

永遠(yuǎn)不要池化虛擬線程

之前我們提到過(guò),虛擬線程的開銷非常低,每個(gè)虛擬線程可能只消耗幾百字節(jié)的內(nèi)存。這意味著我們不需要為虛擬線程創(chuàng)建所謂的線程池。相應(yīng)地,根據(jù)我們之前的編碼習(xí)慣,如果某個(gè)功能只能接受20個(gè)并發(fā)請(qǐng)求,我們可能會(huì)創(chuàng)建一個(gè)固定大小為20的線程池來(lái)進(jìn)行限流。然而,如果使用虛擬線程,我們應(yīng)盡量改用類似信號(hào)量的方式來(lái)實(shí)現(xiàn)。

協(xié)程、IO多路復(fù)用與虛擬線程的關(guān)系

有很多同學(xué)可能對(duì)對(duì)這幾個(gè)概念相對(duì)比較混淆,我們可以來(lái)梳理下這幾個(gè)概念之間的異同,首先協(xié)程與虛擬線程從概念上是相同的,只是不同語(yǔ)言之間的實(shí)現(xiàn)方式是不同的,都是在用戶態(tài)線程中維護(hù)多個(gè)子線程進(jìn)行切換,只是如Python語(yǔ)言可能需要手動(dòng)去喚起協(xié)程,而Java中的虛擬線程都是交由JVM調(diào)度的。

而IO多路復(fù)用,我們以Netty為例,它內(nèi)部使用了IO多路復(fù)用技術(shù)來(lái)管理和處理大量的并發(fā)IO操作。Netty的IO多路復(fù)用機(jī)制可以有效地管理和調(diào)度多個(gè)連接,提高系統(tǒng)的吞吐量和性能。它基于事件驅(qū)動(dòng)的設(shè)計(jì)模型,通過(guò)選擇器(Selector)來(lái)同時(shí)監(jiān)控多個(gè)IO通道的狀態(tài),當(dāng)有IO事件就緒時(shí),通過(guò)回調(diào)機(jī)制來(lái)進(jìn)行處理。而虛擬線程通常用于解決IO阻塞的問(wèn)題,通過(guò)在IO操作或時(shí)間等待點(diǎn)上主動(dòng)釋放執(zhí)行權(quán),來(lái)實(shí)現(xiàn)更好的并發(fā)性能。虛擬線程可以減少線程切換的開銷,但它仍然是在應(yīng)用程序內(nèi)部執(zhí)行的,并不能直接替代IO多路復(fù)用來(lái)管理和處理大量的并發(fā)IO操作。可以說(shuō)術(shù)業(yè)有專攻,但是虛擬線程確實(shí)會(huì)對(duì)多IO操作有效率提升的。

到此這篇關(guān)于淺析Java中的虛擬線程的文章就介紹到這了,更多相關(guān)Java虛擬線程內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 解析Arthas協(xié)助排查線上skywalking不可用問(wèn)題

    解析Arthas協(xié)助排查線上skywalking不可用問(wèn)題

    這篇文章主要為大家介紹了解析Arthas協(xié)助排查線上skywalking不可用的問(wèn)題詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-02-02
  • @MapperScan和@ComponentScan一塊使用導(dǎo)致沖突的解決

    @MapperScan和@ComponentScan一塊使用導(dǎo)致沖突的解決

    這篇文章主要介紹了@MapperScan和@ComponentScan一塊使用導(dǎo)致沖突的解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-11-11
  • 如何把springboot jar項(xiàng)目 改為war項(xiàng)目

    如何把springboot jar項(xiàng)目 改為war項(xiàng)目

    這篇文章主要介紹了如何把springboot jar項(xiàng)目 改為war項(xiàng)目,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-11-11
  • 基于SpringBoot實(shí)現(xiàn)自動(dòng)裝配返回屬性的設(shè)計(jì)思路

    基于SpringBoot實(shí)現(xiàn)自動(dòng)裝配返回屬性的設(shè)計(jì)思路

    這篇文章主要介紹了基于SpringBoot實(shí)現(xiàn)自動(dòng)裝配返回屬性,這里涉及到的技術(shù)知識(shí)點(diǎn)有注解解析器,為什么用ResponseBodyAdvice這里解析?不在Filter,Interceptors,本文結(jié)合示例代碼給大家介紹的非常詳細(xì),需要的朋友參考下吧
    2022-03-03
  • JAVA中split函數(shù)的常見用法實(shí)例

    JAVA中split函數(shù)的常見用法實(shí)例

    Java中我們可以利用split把字符串按照指定的分割符進(jìn)行分割,然后返回字符串?dāng)?shù)組,下面這篇文章主要給大家介紹了關(guān)于JAVA中split函數(shù)的常見用法,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-07-07
  • java用類加載器的5種方式讀取.properties文件

    java用類加載器的5種方式讀取.properties文件

    這篇文章主要介紹了java用類加載器的5種方式讀取.properties文件,詳細(xì)的介紹了這5種方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-11-11
  • SpringBoot之自定義啟動(dòng)異常堆棧信息打印方式

    SpringBoot之自定義啟動(dòng)異常堆棧信息打印方式

    這篇文章主要介紹了SpringBoot之自定義啟動(dòng)異常堆棧信息打印方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-08-08
  • Scala遞歸函數(shù)調(diào)用自身

    Scala遞歸函數(shù)調(diào)用自身

    這篇文章主要介紹了Scala遞歸函數(shù),Scala遞歸函數(shù)是一種函數(shù)可以調(diào)用自身的函數(shù),直到滿足某個(gè)特定的條件為止。在函數(shù)式編程的語(yǔ)言中,遞歸函數(shù)起著重要的作用,因?yàn)樗梢杂脕?lái)表示循環(huán)或迭代的邏輯
    2023-04-04
  • Java三目運(yùn)算符用法舉例

    Java三目運(yùn)算符用法舉例

    三目運(yùn)算符是我們經(jīng)常在代碼中使用的,這篇文章主要給大家介紹了關(guān)于Java三目運(yùn)算符用法的相關(guān)資料,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2023-11-11
  • Java運(yùn)行時(shí)多態(tài)性的實(shí)現(xiàn)

    Java運(yùn)行時(shí)多態(tài)性的實(shí)現(xiàn)

    Java運(yùn)行時(shí)多態(tài)性的實(shí)現(xiàn)...
    2006-12-12

最新評(píng)論