科技與狠活仅叫?JDK19中的虛擬線程到底什么鬼?

最近糙捺,JDK 19發(fā)布了诫咱,推出了幾個(gè)新的特性,其中有一個(gè)比較值得關(guān)注的那就是新增了虛擬線程继找。

很多人可能比較疑惑遂跟,到底什么是虛擬線程逃沿,和我們現(xiàn)在使用的平臺(tái)線程有啥區(qū)別呢婴渡?

要說(shuō)清楚JDK 19中的虛擬線程,我們要先來(lái)了解一下線程都是怎么實(shí)現(xiàn)的凯亮。

線程的實(shí)現(xiàn)方式

我們都知道边臼,在操作系統(tǒng)中,線程是比進(jìn)程更輕量級(jí)的調(diào)度執(zhí)行單位假消,線程的引入可以把一個(gè)進(jìn)程的資源分配和執(zhí)行調(diào)度分開(kāi)柠并,各個(gè)線程既可以共享進(jìn)程資源,又可以獨(dú)立調(diào)度富拗。

其實(shí)臼予,線程的實(shí)現(xiàn)方式主要有三種:分別是使用內(nèi)核線程實(shí)現(xiàn)、使用用戶線程實(shí)現(xiàn)以及使用用戶線程加輕量級(jí)進(jìn)程混合實(shí)現(xiàn)啃沪。

使用內(nèi)核線程實(shí)現(xiàn)

內(nèi)核線程(Kernel-Level Thread,KLT)就是直接由操作系統(tǒng)內(nèi)核(Kernel粘拾,下稱(chēng)內(nèi)核)支持的線程,這種線程由內(nèi)核來(lái)完成線程切換创千,內(nèi)核通過(guò)操縱調(diào)度器(Scheduler)對(duì)線程進(jìn)行調(diào)度缰雇,并負(fù)責(zé)將線程的任務(wù)映射到各個(gè)處理器上入偷,并向應(yīng)用程序提供API接口來(lái)管理線程。

應(yīng)用程序一般不會(huì)直接去使用內(nèi)核線程械哟,而是去使用內(nèi)核線程的一種高級(jí)接口——輕量級(jí)進(jìn)程(Light Weight Process,LWP)疏之,輕量級(jí)進(jìn)程就是我們通常意義上所講的線程,由于每個(gè)輕量級(jí)進(jìn)程都由一個(gè)內(nèi)核線程支持暇咆,因此只有先支持內(nèi)核線程锋爪,才能有輕量級(jí)進(jìn)程。

有了內(nèi)核線程的支持爸业,每個(gè)輕量級(jí)進(jìn)程都成為一個(gè)獨(dú)立的調(diào)度單元几缭,即使有一個(gè)輕量級(jí)進(jìn)程在系統(tǒng)調(diào)用中阻塞了,也不會(huì)影響整個(gè)進(jìn)程繼續(xù)工作沃呢。

但是輕量級(jí)進(jìn)程具有它的局限性:首先年栓,由于是基于內(nèi)核線程實(shí)現(xiàn)的,所以各種線程操作薄霜,如創(chuàng)建某抓、析構(gòu)及同步,都需要進(jìn)行系統(tǒng)調(diào)用惰瓜。而系統(tǒng)調(diào)用的代價(jià)相對(duì)較高否副,需要在用戶態(tài)(User Mode)和內(nèi)核態(tài)(Kernel Mode)中來(lái)回切換。其次崎坊,每個(gè)輕量級(jí)進(jìn)程都需要有一個(gè)內(nèi)核線程的支持备禀,因此輕量級(jí)進(jìn)程要消耗一定的內(nèi)核資源(如內(nèi)核線程的棧空間)奈揍,因此一個(gè)系統(tǒng)支持輕量級(jí)進(jìn)程的數(shù)量是有限的曲尸。

使用用戶線程實(shí)現(xiàn)

在用戶空間建立線程庫(kù),通過(guò)運(yùn)行時(shí)系統(tǒng)(Run-time System)來(lái)完成線程的管理男翰,因?yàn)檫@種線程的實(shí)現(xiàn)是在用戶空間的另患,所以操作系統(tǒng)的內(nèi)核并不知道線程的存在,所以?xún)?nèi)核管理的還是進(jìn)程蛾绎,所以這種線程的切換不需要內(nèi)核操作昆箕。

這種實(shí)現(xiàn)方式下,一個(gè)進(jìn)程和線程之間的關(guān)系是一對(duì)多的租冠。

這種線程實(shí)現(xiàn)方式的優(yōu)點(diǎn)是線程切換快鹏倘,并且可以運(yùn)行在任何操作系統(tǒng)之上,只需要實(shí)現(xiàn)線程庫(kù)就行了顽爹。但是缺點(diǎn)也比較明顯纤泵,就是所有線程的操作都需要用戶程序自己處理,并且因?yàn)榇蠖鄶?shù)系統(tǒng)調(diào)用都是阻塞的话原,所以一旦一個(gè)進(jìn)程阻塞了夕吻,那么進(jìn)程中的所有線程也會(huì)被阻塞诲锹。還有就是多處理器系統(tǒng)中如何將線程映射到其他處理器上也是一個(gè)比較大的問(wèn)題。

使用用戶線程加輕量級(jí)進(jìn)程混合實(shí)現(xiàn)

還有一種混合實(shí)現(xiàn)的方式涉馅,就是線程的創(chuàng)建在用戶空間完成归园,通過(guò)線程庫(kù)進(jìn)行,但是線程的調(diào)度是由內(nèi)核來(lái)完成的稚矿。多個(gè)用戶線程通過(guò)多路復(fù)用來(lái)復(fù)用多個(gè)內(nèi)核線程庸诱。這個(gè)就不展開(kāi)講了

Java線程的實(shí)現(xiàn)方式

以上講的是操作系統(tǒng)的線程的實(shí)現(xiàn)的三種方式,不同的操作系統(tǒng)在實(shí)現(xiàn)線程的時(shí)候會(huì)采用不同的機(jī)制晤揣,比如windows采用的是內(nèi)核線程實(shí)現(xiàn)的桥爽,而Solaris則是通過(guò)混合模式實(shí)現(xiàn)的。

而Java作為一門(mén)跨平臺(tái)的編程語(yǔ)言昧识,實(shí)際上他的線程的實(shí)現(xiàn)其實(shí)是依賴(lài)具體的操作系統(tǒng)的钠四。而比較常用的windows和linux來(lái)說(shuō),都是采用內(nèi)核線程的方式實(shí)現(xiàn)的跪楞。

也就是說(shuō)缀去,當(dāng)我們?cè)贘AVA代碼中創(chuàng)建一個(gè)Tread的時(shí)候,其實(shí)是需要映射到操作系統(tǒng)的線程的具體實(shí)現(xiàn)的甸祭,因?yàn)槌R?jiàn)的通過(guò)內(nèi)核線程實(shí)現(xiàn)的方式在創(chuàng)建缕碎、調(diào)度時(shí)都需要進(jìn)行內(nèi)核參與,所以成本比較高池户,盡管JAVA中提供了線程池的方式來(lái)避免重復(fù)創(chuàng)建線程咏雌,但是依舊有很大的優(yōu)化空間。而且這種實(shí)現(xiàn)方式意味著受機(jī)器資源的影響校焦,平臺(tái)線程數(shù)也是有限制的赊抖。

虛擬線程

JDK 19引入的虛擬線程,是JDK 實(shí)現(xiàn)的輕量級(jí)線程斟湃,他可以避免上下文切換帶來(lái)的的額外耗費(fèi)熏迹。他的實(shí)現(xiàn)原理其實(shí)是JDK不再是每一個(gè)線程都一對(duì)一的對(duì)應(yīng)一個(gè)操作系統(tǒng)的線程了檐薯,而是會(huì)將多個(gè)虛擬線程映射到少量操作系統(tǒng)線程中凝赛,通過(guò)有效的調(diào)度來(lái)避免那些上下文切換。

而且坛缕,我們可以在應(yīng)用程序中創(chuàng)建非常多的虛擬線程墓猎,而不依賴(lài)于平臺(tái)線程的數(shù)量。這些虛擬線程是由JVM管理的赚楚,因此它們不會(huì)增加額外的上下文切換開(kāi)銷(xiāo)毙沾,因?yàn)樗鼈冏鳛槠胀↗ava對(duì)象存儲(chǔ)在RAM中。

虛擬線程與平臺(tái)線程的區(qū)別

首先宠页,虛擬線程總是守護(hù)線程左胞。setDaemon (false)方法不能將虛擬線程更改為非守護(hù)線程寇仓。所以,需要注意的是烤宙,當(dāng)所有啟動(dòng)的非守護(hù)進(jìn)程線程都終止時(shí)遍烦,JVM將終止。這意味著JVM不會(huì)等待虛擬線程完成后才退出躺枕。

其次服猪,即使使用setPriority()方法,虛擬線程始終具有normal的優(yōu)先級(jí)拐云,且不能更改優(yōu)先級(jí)罢猪。在虛擬線程上調(diào)用此方法沒(méi)有效果。

還有就是叉瘩,虛擬線程是不支持stop()膳帕、suspend()或resume()等方法。這些方法在虛擬線程上調(diào)用時(shí)會(huì)拋出UnsupportedOperationException異常薇缅。

如何使用虛擬線程

接下來(lái)介紹一下综苔,在JDK 19中如何使用虛擬線程估脆。

首先,通過(guò)Thread.startVirtualThread()可以運(yùn)行一個(gè)虛擬線程:

Thread.startVirtualThread(() -> {
    System.out.println("虛擬線程執(zhí)行中...");
});

其次,通過(guò)Thread.Builder也可以創(chuàng)建虛擬線程惊窖,Thread類(lèi)提供了ofPlatform()來(lái)創(chuàng)建一個(gè)平臺(tái)線程、ofVirtual()來(lái)創(chuàng)建虛擬現(xiàn)場(chǎng)丛肢。

Thread.Builder platformBuilder = Thread.ofPlatform().name("平臺(tái)線程");
Thread.Builder virtualBuilder = Thread.ofVirtual().name("虛擬線程");

Thread t1 = platformBuilder .start(() -> {...}); 
Thread t2 = virtualBuilder.start(() -> {...});

另外闯睹,線程池也支持了虛擬線程,可以通過(guò)Executors.newVirtualThreadPerTaskExecutor()來(lái)創(chuàng)建虛擬線程:

try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    IntStream.range(0, 10000).forEach(i -> {
        executor.submit(() -> {
            Thread.sleep(Duration.ofSeconds(1));
            return i;
        });
    });
}

但是梧奢,其實(shí)并不建議虛擬線程和線程池一起使用狱掂,因?yàn)镴ava線程池的設(shè)計(jì)是為了避免創(chuàng)建新的操作系統(tǒng)線程的開(kāi)銷(xiāo),但是創(chuàng)建虛擬線程的開(kāi)銷(xiāo)并不大亲轨,所以其實(shí)沒(méi)必要放到線程池中趋惨。

性能差異

說(shuō)了半天,虛擬線程到底能不能提升性能惦蚊,能提升多少呢器虾?我們來(lái)做個(gè)測(cè)試。

我們寫(xiě)一個(gè)簡(jiǎn)單的任務(wù)蹦锋,在控制臺(tái)中打印消息之前等待1秒:

final AtomicInteger atomicInteger = new AtomicInteger();

Runnable runnable = () -> {
  try {
    Thread.sleep(Duration.ofSeconds(1));
  } catch(Exception e) {
      System.out.println(e);
  }
  System.out.println("Work Done - " + atomicInteger.incrementAndGet());
};

現(xiàn)在兆沙,我們將從這個(gè)Runnable創(chuàng)建10,000個(gè)線程,并使用虛擬線程和平臺(tái)線程執(zhí)行它們莉掂,以比較兩者的性能葛圃。

先來(lái)我們比較熟悉的平臺(tái)線程的實(shí)現(xiàn):

Instant start = Instant.now();

try (var executor = Executors.newFixedThreadPool(100)) {
  for(int i = 0; i < 10_000; i++) {
    executor.submit(runnable);
  }
}

Instant finish = Instant.now();
long timeElapsed = Duration.between(start, finish).toMillis();  
System.out.println("總耗時(shí) : " + timeElapsed);

輸出結(jié)果為:

總耗時(shí) : 102323

總耗時(shí)大概100秒左右。接下來(lái)再用虛擬線程跑一下看看

因?yàn)樵贘DK 19中,虛擬線程是一個(gè)預(yù)覽API库正,默認(rèn)是禁用曲楚。所以需要使用$ java——source 19——enable-preview xx.java 的方式來(lái)運(yùn)行代碼。

Instant start = Instant.now();

try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
  for(int i = 0; i < 10_000; i++) {
    executor.submit(runnable);
  }
}

Instant finish = Instant.now();
long timeElapsed = Duration.between(start, finish).toMillis();  
System.out.println("總耗時(shí) : " + timeElapsed);

使用 Executors.newVirtualThreadPerTaskExecutor()來(lái)創(chuàng)建虛擬線程褥符,執(zhí)行結(jié)果如下:

總耗時(shí) : 1674

總耗時(shí)大概1.6秒左右洞渤。

100秒和1.6秒的差距,足以看出虛擬線程的性能提升還是立竿見(jiàn)影的属瓣。

總結(jié)

本文給大家介紹了一下JDK 19新推出的虛擬線程载迄,或者叫協(xié)程,主要是為了解決在讀書(shū)操作系統(tǒng)中線程需要依賴(lài)內(nèi)核線程的實(shí)現(xiàn)抡蛙,導(dǎo)致有很多額外開(kāi)銷(xiāo)的問(wèn)題护昧。通過(guò)在Java語(yǔ)言層面引入虛擬線程,通過(guò)JVM進(jìn)行調(diào)度管理粗截,從而減少上下文切換的成本惋耙。

同時(shí)我們經(jīng)過(guò)簡(jiǎn)單的demo測(cè)試,發(fā)現(xiàn)虛擬線程的執(zhí)行確實(shí)高效了很多熊昌。但是使用的時(shí)候也需要注意绽榛,虛擬線程是守護(hù)線程,所以有可能會(huì)沒(méi)等他執(zhí)行完虛擬機(jī)就會(huì)shutdown掉婿屹。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末灭美,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子昂利,更是在濱河造成了極大的恐慌届腐,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,826評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蜂奸,死亡現(xiàn)場(chǎng)離奇詭異犁苏,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)扩所,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)围详,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人祖屏,你說(shuō)我怎么就攤上這事助赞。” “怎么了赐劣?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,234評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵嫉拐,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我魁兼,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,562評(píng)論 1 293
  • 正文 為了忘掉前任咐汞,我火速辦了婚禮盖呼,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘化撕。我一直安慰自己几晤,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,611評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布植阴。 她就那樣靜靜地躺著蟹瘾,像睡著了一般。 火紅的嫁衣襯著肌膚如雪掠手。 梳的紋絲不亂的頭發(fā)上憾朴,一...
    開(kāi)封第一講書(shū)人閱讀 51,482評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音喷鸽,去河邊找鬼众雷。 笑死,一個(gè)胖子當(dāng)著我的面吹牛做祝,可吹牛的內(nèi)容都是我干的砾省。 我是一名探鬼主播,決...
    沈念sama閱讀 40,271評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼混槐,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼编兄!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起声登,我...
    開(kāi)封第一講書(shū)人閱讀 39,166評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤翻诉,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后捌刮,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體碰煌,經(jīng)...
    沈念sama閱讀 45,608評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,814評(píng)論 3 336
  • 正文 我和宋清朗相戀三年绅作,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了芦圾。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,926評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡俄认,死狀恐怖个少,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情眯杏,我是刑警寧澤夜焦,帶...
    沈念sama閱讀 35,644評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站岂贩,受9級(jí)特大地震影響茫经,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,249評(píng)論 3 329
  • 文/蒙蒙 一卸伞、第九天 我趴在偏房一處隱蔽的房頂上張望抹镊。 院中可真熱鬧,春花似錦荤傲、人聲如沸垮耳。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,866評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)终佛。三九已至,卻和暖如春雾家,著一層夾襖步出監(jiān)牢的瞬間铃彰,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,991評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工榜贴, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留豌研,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,063評(píng)論 3 370
  • 正文 我出身青樓唬党,卻偏偏與公主長(zhǎng)得像鹃共,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子驶拱,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,871評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容