Java21新特性-虛擬線程
虛擬線程是輕量級線程(類似于 Go 中的 “協程(Goroutine)”),可以減少編寫、維護和調度高吞吐量并發應用程序的工作量。
線程是可供調度的最小處理單元,它與其他類似的處理單元并發運行,并且在很大程度上是獨立運行的。線程(java.lang.Thread)有兩種,平臺線程和虛擬線程。
一. Java內部線程實現模式
綠色線程(Green Thread):遠古時期,Java使用綠色線程模式。這個模式下,多線程的調度和管理有JVM完成。綠色線程模式才作用M:1線程映射模型。這里就有一個問題,Java不能夠規模化管理這種線程,也就無法充分發揮硬件性能。同樣的實現綠色線程也是一件非常有挑戰性的事情,因為它需要非常底層的支持才能夠良好運行。隨后Java移除了綠色線程,轉而使用本地線程。這使得Java的線程執行比綠色線程更慢。
平臺線程(Platform Thread):從Java 1.2開始從綠色線程切換到了平臺線程模式(有些人稱之為本地線程(Native Thread))。在操作系統的幫助下,JVM 得以控制平臺線程。平臺線程的執行效率很高,但是開啟和關閉他們的資源消耗較大。這就是為什么我們現在要使用線程池。這個模型遵循著 1:1 線程映射,即一個Java線程映射到一個內核線程。當一個Java線程被創建時,相應的一個對應的核心線程也會被創建,用來執行線程代碼。自此之后,平臺線程模型的做法就延續到了今天。
1.1 當前Java線程模型有什么問題嗎?
- 只是對于操作系統內核線程的一個簡單包裝,真正的線程調度,還是由操作系統完成;
- 因為線程的創建和銷毀都需要系統內核完成,涉及用戶態切換,資源消耗較大;
- 本地線程需要保存他們的調用棧在內存中,大概2MB~20MB的預留空間。如果你有4GB內存,如果每個線程占用20MB內存,那么你就只能創建大概200個線程;
- 因為本地線程是一種系統資源,加載一個新的本地線程大概需要1毫秒;
- 上下文切換代價昂貴,需要一個到內核的系統調用;
- 上面這些強制性的限制會限制線程創建的數量,同時會導致性能下降和過度的內存消耗。因為我們不能創建更多的線程;
- 我們不能通過增加更多的線程來增應用規模,因為上下文切換和內存占用的代價高昂;
1.2 一個IO密集型應用的例子
考慮一臺16GB內存的網絡服務器。對于每個服務請求,都分配一個不同的線程。我們假設每個線程需要20MB內存空間,那么這臺機器可以支持800個線程。當前,后端的API一般使用REST/SOAP調用方式,例如數據庫操作和API信息轉發這些IO密集型操作。由此可見,后端服務的主要是IO密集型而不是CPU密集型。
接著假設一下,一個IO操作需要100毫秒,請求執行(IO密集型)需要100毫秒,以及返回結果也需要100毫秒。同時,當每秒有800個請求時,線程數得到了最大容量。
讓我們來計算一下單個請求的CPU占用時間
CPU時間?。健≌埱鬁蕚鋾r間 + 返回結果準備時間
= 0.1ms + 0.1ms
= 0.2ms
對于800個請求呢?
800個線程的請求時間= 800 * 0.2ms
= 160ms
受限于我們的內存容量,我們只能創建800個請求,也就導致了我們CPU使用率并不高
CPU使用率=160ms / 1000ms
= 16%
那么如何才能使CPU的利用率到達90%呢?
16% = 800個線程
90% = X個線程
X = 4500
但是我們當前因為內存的限制不能創建那么多的線程,除非我們能突破這個限制,擁有90G內存。
90G的內存是一個比較離譜的數字,所以說創建本地線程很明顯不能充分利用硬件資源。
二. 虛擬線程
虛擬線程是一個Java線程的輕量級實現版本,最早于JDK19中出現,當前仍是預覽狀態,可以通過Jvm配置項開啟。
虛擬線程是JVM項目loom的一部分
虛擬線程解決了傳遞和維護本地線程的瓶頸問題,同時可以用之編寫高吞吐的并發應用,榨干硬件資源的潛力。
與本地線程不同,虛擬線程并不有操作系統控制,虛擬線程是一個有JVM管理的用戶態線程。對比于本地線程的高資源占用,每個虛擬線程只需要幾個字節的內存空間。這是的它更適合控制管理大量的用戶訪問,或者說處理IO密集型任務。
在創建虛擬線程的數量上幾乎沒有限制,甚至可以創建一百萬個,因為虛擬線程并不需要來自內核的系統調用。
在虛擬線程如此輕量化的條件下,線程池不再成為必須品,只需要在需要的時候盡情創建虛擬線程就好。
虛擬線程和傳統的本地線程操作完全兼容,例如本地線程變量,同步塊,線程中斷,等等。
2.1 虛擬線程如何工作
虛擬線程是一種輕量級(用戶模式)線程,這種線程是由Java虛擬機調度,而不是操作系統。虛擬線程占用空間小,任務切換開銷幾乎可以忽略不計,因此可以極大量地創建和使用。總體來看,虛擬線程實現如下:
virtual thread = continuation + scheduler
虛擬線程會把任務(一般是java.lang.Runnable)包裝到一個Continuation實例中:
- 當任務需要阻塞掛起的時候,會調用
Continuation的yield操作進行阻塞 - 當任務需要解除阻塞繼續執行的時候,
Continuation會被繼續執行
Scheduler也就是執行器,會把任務提交到一個載體線程池中執行:
- 執行器是
java.util.concurrent.Executor的子類 - 虛擬線程框架提供了一個默認的
ForkJoinPool用于執行虛擬線程任務
下文會把carrier thread稱為"載體線程",指的是負責執行虛擬線程中任務的平臺線程,或者說運行虛擬線程的平臺線程稱為它的載體線程
操作系統調度系統線程,而Java平臺線程與系統線程一一映射,所以平臺線程被操作系統調度,但是虛擬線程是由JVM調度。JVM把虛擬線程分配給平臺線程的操作稱為mount(掛載),反過來取消分配平臺線程的操作稱為unmount(卸載):
mount操作:虛擬線程掛載到平臺線程,虛擬線程中包裝的Continuation棧數據幀或者引用棧數據會被拷貝到平臺線程的線程棧,這是一個從堆復制到棧的過程unmount操作:虛擬線程從平臺線程卸載,大多數虛擬線程中包裝的Continuation棧數據幀會留在堆內存中
這個mount -> run -> unmount過程用偽代碼表示如下:
mount();
try {
Continuation.run();
} finally {
unmount();
}
JVM 使用 M:N 來完成虛擬線程與本地線程的映射。

2.2 虛擬線程和線程池的異同
看上去虛擬線程和線程池有類似之處,都是利用M個內核線程,完成N個任務,而避免平臺線程頻繁的創建和銷毀。但他們是有本質區別的:
- 線程池中的正在執行的任務只有到任務執行完成后,才會釋放平臺線程,如果某個任務在執行過程中發生IO阻塞也不會被掛起執行其他任務。
- 虛擬線程中運行的代碼調用阻塞I/O操作時,Java運行時會掛起虛擬線程,然后切換到另一個可執行的虛擬線程,直到它可以恢復為止。
三. 虛擬線程的使用
官方提供了以下四種方式創建虛擬線程:
- 使用
Thread.startVirtualThread()創建 - 使用
Thread.ofVirtual()創建 - 使用
ThreadFactory創建
3.1 使用 Thread.startVirtualThread() 創建
public class VirtualThreadTest {
public static void main(String[] args) {
CustomThread customThread = new CustomThread();
Thread.startVirtualThread(customThread);
}
}
static class CustomThread implements Runnable {
@Override
public void run() {
System.out.println("CustomThread run");
}
}
3.2 使用 Thread.ofVirtual()創建
public class VirtualThreadTest {
public static void main(String[] args) {
CustomThread customThread = new CustomThread();
// 創建不啟動
Thread unStarted = Thread.ofVirtual().unstarted(customThread);
unStarted.start();
// 創建直接啟動
Thread.ofVirtual().start(customThread);
}
}
static class CustomThread implements Runnable {
@Override
public void run() {
System.out.println("CustomThread run");
}
}
3.3 使用 ThreadFactory 創建
public class VirtualThreadTest {
public static void main(String[] args) {
CustomThread customThread = new CustomThread();
ThreadFactory factory = Thread.ofVirtual().factory();
Thread thread = factory.newThread(customThread);
thread.start();
}
}
static class CustomThread implements Runnable {
@Override
public void run() {
System.out.println("CustomThread run");
}
}
Java虛擬線程 - 昕希 - 博客園 (cnblogs.com)
Java21手冊(一):虛擬線程 Virtual Threads - 掘金 (juejin.cn)
Java 21 正式 GA,虛擬線程真的來了 - calvinit - 博客園 (cnblogs.com)

浙公網安備 33010602011771號