虛擬線程的引入與優(yōu)勢
在Loom項(xiàng)目之前,Java虛擬機(jī)(JVM)中的線程是通過java.lang.Thread類型來實(shí)現(xiàn)的,這些線程被稱為平臺線程。
然而,平臺線程的創(chuàng)建和維護(hù)在資源使用上存在顯著的開銷。首先,創(chuàng)建成本不菲,因?yàn)槊慨?dāng)操作系統(tǒng)需要?jiǎng)?chuàng)建一個(gè)新的平臺線程時(shí),它必須分配大量的內(nèi)存(通常以兆字節(jié)計(jì))來存儲線程的上下文信息、本機(jī)棧和Java調(diào)用棧。這一過程受到固定大小堆棧的限制,導(dǎo)致創(chuàng)建和調(diào)度平臺線程時(shí)的開銷在空間和時(shí)間上都相當(dāng)巨大。此外,當(dāng)調(diào)度器需要從當(dāng)前執(zhí)行的線程中搶占時(shí),必須處理大量內(nèi)存的移動,這進(jìn)一步增加了操作的復(fù)雜性和成本。這種開銷不僅限制了可以同時(shí)創(chuàng)建的線程數(shù)量,而且也容易導(dǎo)致內(nèi)存資源的耗盡。以下是一個(gè)示例,展示了在Java中如何通過不斷實(shí)例化新的平臺線程,迅速達(dá)到內(nèi)存耗盡的情況:
private static void stackOverflowErrorDemo() { try { int threadCount = 0; // 嘗試創(chuàng)建高達(dá)百萬級的線程數(shù)量 while (threadCount++ < 100000000) { // 創(chuàng)建并啟動一個(gè)新線程 Thread thread = new Thread(() -?> { try { // 線程休眠1秒,模擬長時(shí)間運(yùn)行的任務(wù) Thread.sleep(Duration.ofSeconds(1)); } catch (InterruptedException e) { // 如果線程被中斷,將其轉(zhuǎn)換為運(yùn)行時(shí)異常 throw new RuntimeException(e); } }); // 啟動線程 thread.start(); } } catch (RuntimeException e) { // 捕獲并處理由線程啟動過程中可能拋出的運(yùn)行時(shí)異常 e.printStackTrace(); } }
在實(shí)際操作中,達(dá)到OutOfMemoryError的時(shí)間會根據(jù)操作系統(tǒng)和硬件的不同而有所差異。然而,通常情況下,這個(gè)過程可以在極短的時(shí)間內(nèi)完成。
為了解決這些問題,虛擬線程應(yīng)運(yùn)而生。
虛擬線程的優(yōu)勢
資源效率:虛擬線程在內(nèi)存使用上更為高效,初始內(nèi)存占用通常只有幾百字節(jié),遠(yuǎn)小于平臺線程所需的幾兆字節(jié)。
簡化線程管理:虛擬線程的創(chuàng)建和管理過程更為簡便,通過工廠方法可以輕松創(chuàng)建,無需手動管理線程資源。
避免線程爆炸:由于資源消耗低,虛擬線程可以處理大量并發(fā)任務(wù),而不必?fù)?dān)心資源耗盡。
協(xié)作調(diào)度:虛擬線程采用協(xié)作調(diào)度模型,減少了鎖競爭和上下文切換的開銷,提升了多線程程序的性能。
避免阻塞:虛擬線程在遇到阻塞操作時(shí)可以釋放執(zhí)行權(quán),允許其他線程執(zhí)行,提高了程序的響應(yīng)性。
虛擬線程如何創(chuàng)建
創(chuàng)建虛擬線程是Java中的一項(xiàng)新特性,它旨在解決傳統(tǒng)平臺線程所面臨的資源限制問題。虛擬線程作為java.lang.Thread的一個(gè)替代實(shí)現(xiàn),其獨(dú)特之處在于將線程的調(diào)用堆棧存儲在Java堆內(nèi)存中,而不是傳統(tǒng)的本地線程堆棧中。這種方式顯著減少了每個(gè)線程所需的初始內(nèi)存占用,通常僅為幾百字節(jié),而不是幾兆字節(jié)。更進(jìn)一步,虛擬線程的堆棧大小是動態(tài)可變的,這使得我們無需為各種用例預(yù)分配大量內(nèi)存。以下是創(chuàng)建虛擬線程的兩種方法:
使用工廠方法創(chuàng)建虛擬線程
通過java.lang.Thread的ofVirtual靜態(tài)工廠方法,我們可以輕松創(chuàng)建虛擬線程。首先,定義一個(gè)輔助函數(shù)來創(chuàng)建并啟動一個(gè)帶有指定名稱的虛擬線程:
private static Thread createVirtualThread(String name, Runnable runnable) {
return Thread.ofVirtual()
.name(name)
.start(runnable);
}
使用ThreadPerTaskExecutor創(chuàng)建虛擬線程
另一種方法是使用專為虛擬線程設(shè)計(jì)的java.util.concurrent.ExecutorService實(shí)現(xiàn),即ThreadPerTaskExecutor。這個(gè)執(zhí)行器為提交的每個(gè)任務(wù)創(chuàng)建一個(gè)新的虛擬線程:
@SneakyThrows
static void createVirtualThreadUsingExecutorsWithName() {
final ThreadFactory factory = Thread.ofVirtual().name("worker-", 0).factory();
try (var executor = Executors.newThreadPerTaskExecutor(factory)) {
var cleanTime =
executor.submit(
() -> {
log.info("我要打掃衛(wèi)生");
sleep(Duration.ofMillis(500L));
log.info("衛(wèi)生打掃完了");
});
var boilingWater =
executor.submit(
() -> {
log.info("我要去燒一些水");
sleep(Duration.ofSeconds(1L));
log.info("水燒好了");
});
cleanTime.get();
boilingWater.get();
}
}
在這個(gè)示例中,我們使用了submit方法來啟動虛擬線程,它需要一個(gè)Runnable或Callable任務(wù)。submit方法返回一個(gè)Future對象,該對象可以用來跟蹤和控制虛擬線程的執(zhí)行。
虛擬線程的啟動和同步
與平臺線程相比,虛擬線程的啟動和同步方式略有不同,因?yàn)樗鼈兪峭ㄟ^ExecutorService來管理的。每個(gè)submit調(diào)用都返回一個(gè)Future對象,這允許我們跟蹤任務(wù)的狀態(tài),甚至在必要時(shí)阻塞當(dāng)前線程直到虛擬線程完成其任務(wù)。
虛擬線程的原理

??
如上圖所示展示虛擬線程與平臺線程之間的關(guān)系:
JVM維護(hù)了一個(gè)由專用ForkJoinPool創(chuàng)建和維護(hù)的平臺線程池。最初,平臺線程的數(shù)量等于CPU核心的數(shù)量,最多不能超過256個(gè)。
對于每個(gè)創(chuàng)建的虛擬線程,JVM都會將其執(zhí)行調(diào)度到一個(gè)平臺線程上,臨時(shí)將虛擬線程的堆棧塊從堆復(fù)制到平臺線程的堆棧中。我們說平臺線程變成了虛擬線程的載體線程。
我們可以通過運(yùn)行使用ThreadPerTaskExecutor創(chuàng)建虛擬線程的用例,觀察其中的一條日志來說明執(zhí)行過程:
10:30:35.390 [worker-1] INFO in.rcard.virtual.threads.App - VirtualThread[#23,worker-1]/runnable@ForkJoinPool-1-worker-2 | 我要去燒一些水
從日志中進(jìn)行觀察
1. 線程標(biāo)識與命名:每個(gè)虛擬線程都有一個(gè)唯一的標(biāo)識符和名稱,例如 `VirtualThread[#23,worker-1]`。這里的 `#23` 表示線程的編號,而 `worker-1` 是線程的名稱,它們共同幫助開發(fā)者識別和調(diào)試線程。
2. 載體線程的分配:虛擬線程執(zhí)行時(shí),會綁定到一個(gè)特定的載體線程(即平臺線程)。例如,`ForkJoinPool-1-worker-2` 表示該虛擬線程正在由默認(rèn)的ForkJoinPool中的第二個(gè)工作線程執(zhí)行。
3. 阻塞與釋放:當(dāng)虛擬線程遇到阻塞操作時(shí),其載體線程會被釋放,以便能夠執(zhí)行其他就緒的虛擬線程。同時(shí),虛擬線程的堆棧塊會從載體線程的堆棧復(fù)制回Java堆中,以等待阻塞操作的完成。
4. 再次調(diào)度:一旦虛擬線程完成其阻塞操作,調(diào)度器會將其重新排入執(zhí)行隊(duì)列。虛擬線程可能會繼續(xù)在先前的載體線程上執(zhí)行,或者根據(jù)調(diào)度器的決策,在不同的載體線程上繼續(xù)執(zhí)行。
剛才我們提到,默認(rèn)情況下,JVM會創(chuàng)建與cpu核心數(shù)量相等的載體線程(平臺線程),以確保每個(gè)物理核心都能被有效利用。那么假如計(jì)算機(jī)上配備了2個(gè)物理核心和通過超線程技術(shù)支持的4個(gè)邏輯核心,基于此硬件配置,我們可以設(shè)計(jì)一個(gè)程序,該程序旨在生成與邏輯核心數(shù)相匹配的虛擬線程數(shù)量,即4個(gè)虛擬線程。然而,為了探索線程調(diào)度的靈活性,我們可以增加一個(gè)額外的虛擬線程,使得總數(shù)達(dá)到5個(gè),即期望5個(gè)虛擬線程在4個(gè)載體線程上執(zhí)行,那么至少會有一個(gè)載體線程會被重復(fù)使用。執(zhí)行以下程序
static void viewCarrierThreadPoolSize() {
final ThreadFactory factory = Thread.ofVirtual().name("worker-", 0).factory();
try (var executor = Executors.newThreadPerTaskExecutor(factory)) {
IntStream.range(0, numberOfCores() + 1)
.forEach(i -> executor.submit(() -> {
log.info("virtual thread number " + i);
sleep(Duration.ofSeconds(1L));
}));
}
}
[worker-0] INFO in.rcard.virtual.threads.App - VirtualThread[#21,worker-0]/runnable@ForkJoinPool-1-worker-1 | virtual thread number 0 [worker-1] INFO in.rcard.virtual.threads.App - VirtualThread[#23,worker-1]/runnable@ForkJoinPool-1-worker-2 | virtual thread number 1 [worker-2] INFO in.rcard.virtual.threads.App - VirtualThread[#24,worker-2]/runnable@ForkJoinPool-1-worker-3 | virtual thread number 2 [worker-4] INFO in.rcard.virtual.threads.App - VirtualThread[#26,worker-4]/runnable@ForkJoinPool-1-worker-4 | virtual thread number 4 [worker-3] INFO in.rcard.virtual.threads.App - VirtualThread[#25,worker-3]/runnable@ForkJoinPool-1-worker-4 | virtual thread number 3
觀察日志,有四個(gè)載體線程,分別是ForkJoinPool-1-worker-1、ForkJoinPool-1-worker-2、ForkJoinPool-1-worker-3和ForkJoinPool-1-worker-4,F(xiàn)orkJoinPool-1-worker-4被重復(fù)使用了兩次,以上假設(shè)正確。
審核編輯 黃宇
-
存儲
+關(guān)注
關(guān)注
13文章
4793瀏覽量
90075 -
操作系統(tǒng)
+關(guān)注
關(guān)注
37文章
7402瀏覽量
129337 -
虛擬
+關(guān)注
關(guān)注
0文章
199瀏覽量
24281 -
虛擬機(jī)
+關(guān)注
關(guān)注
1文章
972瀏覽量
30488 -
線程
+關(guān)注
關(guān)注
0文章
509瀏覽量
20829
發(fā)布評論請先 登錄
摩爾線程正式開源TileLang-MUSA項(xiàng)目
小馬智行與摩爾線程達(dá)成戰(zhàn)略合作
【瑞薩FPB-RA6E2試用】【瑞薩FPB-RA6E2】RTOS(Real-Time Operating System,實(shí)時(shí)操作系統(tǒng))《線程》個(gè)人理解及項(xiàng)目實(shí)現(xiàn)
解析Linux的進(jìn)程、線程和協(xié)程
多線程的系統(tǒng)
Linux多線程對比單線程的優(yōu)勢
摩爾線程新一代大語言模型對齊框架URPO入選AAAI 2026
車載軟件vECU虛擬化測試解決方案
UVC+MSC實(shí)現(xiàn)中MSC線程未運(yùn)行的原因?
多線程的安全注意事項(xiàng)
鴻蒙5開發(fā)寶藏案例分享---跨線程性能優(yōu)化指南
摩爾線程GPU成功適配Deepseek-V3-0324大模型
探索虛擬線程:原理與實(shí)現(xiàn)
評論