Java21新特性——虛擬線程
Java21最重要的新特性之一是虛擬線程(Virtual Threads)。 傳統(tǒng)的Java線程受制于操作系統(tǒng)的線程數(shù),并發(fā)能力和可伸縮性有限,許多時(shí)候資源無法充分利用。而虛擬線程則提供了一種更高效、更輕量級(jí)的線程模型。虛擬線程,也稱為“用戶模式線程(user-mode threads)”或“纖程(fibers)”。該功能旨在簡(jiǎn)化并發(fā)編程并提供更好的可擴(kuò)展性。虛擬線程是輕量級(jí)的,它們可以比傳統(tǒng)線程創(chuàng)建更多數(shù)量,并且開銷要少得多。
本文主要介紹Java傳統(tǒng)的線程和虛擬線程的特點(diǎn)和區(qū)別,以及虛擬線程的編碼方法和注意事項(xiàng)。
傳統(tǒng)的線程
在舊的Java版本中使用的線程依賴于操作系統(tǒng)的線程,創(chuàng)建線程、銷毀線程以及線程切換都需要大量性能開銷。而操作系統(tǒng)的線程數(shù)有限,當(dāng)應(yīng)用系統(tǒng)需要大量線程的時(shí)候,可能會(huì)導(dǎo)致系統(tǒng)資源耗竭,性能下降,甚至導(dǎo)致系統(tǒng)奔潰。在舊的Java版本中,我們所使用java.lang.Thread來定義線程,這個(gè)就是由操作系統(tǒng)所支持的線程。這種線程通常以1:1的比例映射到OS調(diào)度的內(nèi)核。OS線程相當(dāng)“重”。根據(jù)操作系統(tǒng)配置,默認(rèn)情況下,每個(gè)線程消耗2到10 MB, 因此,如果想在發(fā)應(yīng)用程序中使用一百萬個(gè)線程,那么就要求有超過2TB的內(nèi)存可供使用!很明顯,這就限制了線程數(shù)量。
在基于Java的Web應(yīng)用中,每個(gè)請(qǐng)求使用一個(gè)線程有很多優(yōu)點(diǎn),比如狀態(tài)管理和清理更加容易。但它也造成了可擴(kuò)展性的限制。容易使CPU或網(wǎng)絡(luò)資源耗盡。
虛擬線程
Java21引入虛擬線程,使得Java應(yīng)用程序的線程不再受制于操作系統(tǒng),可以在應(yīng)用中創(chuàng)建多達(dá)數(shù)十億的線程,更好地適應(yīng)各種高并發(fā)場(chǎng)景,提供更高的并發(fā)能力。虛擬線程具有以下優(yōu)點(diǎn):
- 更高的性能:虛擬線程不再受制于操作系統(tǒng)的線程數(shù),并且減少了線程創(chuàng)建、銷毀、共享等操作的性能開銷。從而獲得更高的并發(fā)性能。
- 更高可伸縮性:虛擬線程可以創(chuàng)建多達(dá)數(shù)十億的線程,更能適應(yīng)Java應(yīng)用的大規(guī)模并發(fā)場(chǎng)景。
- 資源消耗更低:虛擬線程比操作系統(tǒng)的線程更加輕量級(jí),資源利用率較高,CPU和內(nèi)存占用較少。
虛擬線程是一個(gè)java.lang.Thread變體,是Project Loom的一部分,不受操作系統(tǒng)的管理或調(diào)度,而是由JVM負(fù)責(zé)調(diào)度。當(dāng)然,任何底層的邏輯都還必須在操作系統(tǒng)線程中運(yùn)行,只是JVM利用載體線程(carrier threads,也就是平臺(tái)線程)之上“攜帶”虛擬線程。
編碼示例
虛擬線程的學(xué)習(xí)成本比較低,只需要像對(duì)待非虛擬線程一樣對(duì)待他們就可以了。
(1) 傳統(tǒng)線程的開發(fā)傳統(tǒng)線程的用法在使用虛擬線程之前我們先來回顧一下傳統(tǒng)的線程的寫法。
Runnable fn = () -> {
// 業(yè)務(wù)代碼
};
Thread thread = new Thread(fn).start();
Project Loom 簡(jiǎn)化了并發(fā)方法,它提供了一種新方法來創(chuàng)建平臺(tái)的線程:
Thread thread = Thread.ofPlatform().
.start(runnable);
或者:
Thread thread = Thread.ofPlatform().
.daemon()
.name("my-custom-thread")
(2) 虛擬線程的用法
API寫法:
Runnable fn = () -> {
// 業(yè)務(wù)代碼
};
Thread thread = Thread.ofVirtual(fn)
.start();
Project Loom 寫法:
Thread thread = Thread.startVirtualThread(() -> {
// 業(yè)務(wù)代碼
});
創(chuàng)建虛擬線程的另一種方法是使用Executor:
var executorService = Executors.newVirtualThreadPerTaskExecutor();
executorService.submit(() -> {
// 業(yè)務(wù)代碼
});
因?yàn)樗械奶摂M線程都是守護(hù)線程,所以如果想在主線程上等待,就需要調(diào)用join()方法,Join方法的作用就是讓主線程等待,當(dāng)有新的線程加入時(shí),主線程會(huì)進(jìn)入等待狀態(tài),一直到調(diào)用方法的副線程執(zhí)行結(jié)束為止。
thread.join();
虛擬線程開發(fā)注意事項(xiàng)
- 注意控制線程數(shù):虛擬線程可以創(chuàng)建大量線程,很容易讓開發(fā)人員不在意其數(shù)量,而過多的線程仍然會(huì)導(dǎo)致性能下降或資源耗盡。因此,仍需根據(jù)資源數(shù)量合理控制應(yīng)用程序的并發(fā)度。
- 注意線程安全:使用虛擬線程時(shí)要注意線程安全性和正確性,避免共享可變狀態(tài)、根據(jù)需要使用同步機(jī)制。
- 注意代碼遷移:在從傳統(tǒng)線程遷移到使用虛擬線程的時(shí)候,需要注意代碼與新環(huán)境、新規(guī)范、新需求的一致性。
總結(jié)
虛擬線程是Java并發(fā)開發(fā)方面的通用、強(qiáng)大的新方法,在Java21版本中已經(jīng)十分成熟了。對(duì)于需要從舊版本JDK遷移到新版本JDK的應(yīng)用程序來說,改造難度并不大,同時(shí)還可以充分利用所有可用硬件資源,提高Java應(yīng)用程序的并發(fā)性和可伸縮性。