[Java基礎]虛擬執行緒

Duancf發表於2024-08-24

虛擬執行緒(Virtual Thread)是 JDK 而不是 OS 實現的輕量級執行緒(Lightweight Process,LWP),由 JVM 排程。許多虛擬執行緒共享同一個作業系統執行緒,虛擬執行緒的數量可以遠大於作業系統執行緒的數量。

虛擬執行緒和平臺執行緒有什麼關係?
在引入虛擬執行緒之前,java.lang.Thread 包已經支援所謂的平臺執行緒(Platform Thread),也就是沒有虛擬執行緒之前,我們一直使用的執行緒。JVM 排程程式透過平臺執行緒(載體執行緒)來管理虛擬執行緒,一個平臺執行緒可以在不同的時間執行不同的虛擬執行緒(多個虛擬執行緒掛載在一個平臺執行緒上),當虛擬執行緒被阻塞或等待時,平臺執行緒可以切換到執行另一個虛擬執行緒。

虛擬執行緒、平臺執行緒和系統核心執行緒的關係圖如下所示(圖源:How to Use Java 19 Virtual Threads):

image

關於平臺執行緒和系統核心執行緒的對應關係多提一點:在 Windows 和 Linux 等主流作業系統中,Java 執行緒採用的是一對一的執行緒模型,也就是一個平臺執行緒對應一個系統核心執行緒。Solaris 系統是一個特例,HotSpot VM 在 Solaris 上支援多對多和一對一。具體可以參考 R 大的回答: JVM 中的執行緒模型是使用者級的麼?。

虛擬執行緒有什麼優點和缺點?
優點
非常輕量級:可以在單個執行緒中建立成百上千個虛擬執行緒而不會導致過多的執行緒建立和上下文切換。
簡化非同步程式設計: 虛擬執行緒可以簡化非同步程式設計,使程式碼更易於理解和維護。它可以將非同步程式碼編寫得更像同步程式碼,避免了回撥地獄(Callback Hell)。
減少資源開銷: 由於虛擬執行緒是由 JVM 實現的,它能夠更高效地利用底層資源,例如 CPU 和記憶體。虛擬執行緒的上下文切換比平臺執行緒更輕量,因此能夠更好地支援高併發場景。
缺點
不適用於計算密集型任務: 虛擬執行緒適用於 I/O 密集型任務,但不適用於計算密集型任務,因為密集型計算始終需要 CPU 資源作為支援。
與某些第三方庫不相容: 雖然虛擬執行緒設計時考慮了與現有程式碼的相容性,但某些依賴平臺執行緒特性的第三方庫可能不完全相容虛擬執行緒。
如何建立虛擬執行緒?
官方提供了以下四種方式建立虛擬執行緒:

使用 Thread.startVirtualThread() 建立
使用 Thread.ofVirtual() 建立
使用 ThreadFactory 建立
使用 Executors.newVirtualThreadPerTaskExecutor()建立
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");
}
}
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、使用 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");
}
}
4、使用Executors.newVirtualThreadPerTaskExecutor()建立

public class VirtualThreadTest {
public static void main(String[] args) {
CustomThread customThread = new CustomThread();
ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
executor.submit(customThread);
}
}
static class CustomThread implements Runnable {
@Override
public void run() {
System.out.println("CustomThread run");
}
}
虛擬執行緒和平臺執行緒效能對比
透過多執行緒和虛擬執行緒的方式處理相同的任務,對比建立的系統執行緒數和處理耗時。

說明:統計建立的系統執行緒中部分為後臺執行緒(比如 GC 執行緒),兩種場景下都一樣,所以並不影響對比。

測試程式碼:

public class VirtualThreadTest {
static List list = new ArrayList<>();
public static void main(String[] args) {
// 開啟執行緒 統計平臺執行緒數
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
scheduledExecutorService.scheduleAtFixedRate(() -> {
ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
ThreadInfo[] threadInfo = threadBean.dumpAllThreads(false, false);
updateMaxThreadNum(threadInfo.length);
}, 10, 10, TimeUnit.MILLISECONDS);

    long start = System.currentTimeMillis();
    // 虛擬執行緒
    ExecutorService executor =  Executors.newVirtualThreadPerTaskExecutor();
    // 使用平臺執行緒
    // ExecutorService executor =  Executors.newFixedThreadPool(200);
    for (int i = 0; i < 10000; i++) {
        executor.submit(() -> {
            try {
                // 執行緒睡眠 0.5 s,模擬業務處理
                TimeUnit.MILLISECONDS.sleep(500);
            } catch (InterruptedException ignored) {
            }
        });
    }
    executor.close();
    System.out.println("max:" + list.get(0) + " platform thread/os thread");
    System.out.printf("totalMillis:%dms\n", System.currentTimeMillis() - start);


}
// 更新建立的平臺最大執行緒數
private static void updateMaxThreadNum(int num) {
    if (list.isEmpty()) {
        list.add(num);
    } else {
        Integer integer = list.get(0);
        if (num > integer) {
            list.add(0, num);
        }
    }
}

}
請求數 10000 單請求耗時 1s:

// Virtual Thread
max:22 platform thread/os thread
totalMillis:1806ms

// Platform Thread 執行緒數200
max:209 platform thread/os thread
totalMillis:50578ms

// Platform Thread 執行緒數500
max:509 platform thread/os thread
totalMillis:20254ms

// Platform Thread 執行緒數1000
max:1009 platform thread/os thread
totalMillis:10214ms

// Platform Thread 執行緒數2000
max:2009 platform thread/os thread
totalMillis:5358ms
請求數 10000 單請求耗時 0.5s:

// Virtual Thread
max:22 platform thread/os thread
totalMillis:1316ms

// Platform Thread 執行緒數200
max:209 platform thread/os thread
totalMillis:25619ms

// Platform Thread 執行緒數500
max:509 platform thread/os thread
totalMillis:10277ms

// Platform Thread 執行緒數1000
max:1009 platform thread/os thread
totalMillis:5197ms

// Platform Thread 執行緒數2000
max:2009 platform thread/os thread
totalMillis:2865ms
可以看到在密集 IO 的場景下,需要建立大量的平臺執行緒非同步處理才能達到虛擬執行緒的處理速度。
因此,在密集 IO 的場景,虛擬執行緒可以大幅提高執行緒的執行效率,減少執行緒資源的建立以及上下文切換。
注意:有段時間 JDK 一直致力於 Reactor 響應式程式設計來提高 Java 效能,但響應式程式設計難以理解、除錯、使用,最終又回到了同步程式設計,最終虛擬執行緒誕生。

虛擬執行緒的底層原理是什麼?

相關文章