作者:Grey
原文地址:
虛擬執行緒(Virtual Threads)是在Project Loom中開發的,並從 Java SE 19 開始作為預覽功能引入 JDK。
線上程模型下,一個 Java 執行緒相當於一個作業系統執行緒,而這些執行緒是很消耗資源的,如果啟動的執行緒過多,會給整個系統的穩定性帶來風險。
虛擬執行緒解決了這個問題,從 Java 程式碼的角度來看,虛擬執行緒感覺就像普通的執行緒,但它們不是 1:1
地對映到作業系統執行緒上。
有一個所謂的載體執行緒池,一個虛擬執行緒被臨時對映到該池中。一旦虛擬執行緒遇到阻塞操作,該虛擬執行緒就會從載體執行緒中移除,而載體執行緒可以執行另一個虛擬執行緒(新的或之前被阻塞的)。
所以阻塞的操作不再阻塞執行的執行緒。這使得我們可以用一個小的載體執行緒池來並行處理大量的請求。
場景
啟動 1000 個任務,每個任務等待一秒鐘(模擬存取外部API),然後返回一個結果(在這個例子中是一個亂數)。
任務類如下
package git.snippets.vt;
import java.util.concurrent.Callable;
import java.util.concurrent.ThreadLocalRandom;
/**
* @author <a href="mailto:[email protected]">Grey</a>
* @date 2022/9/21
* @since 19
*/
public class Task implements Callable<Integer> {
private final int number;
public Task(int number) {
this.number = number;
}
@Override
public Integer call() {
System.out.printf("Thread %s - Task %d waiting...%n", Thread.currentThread().getName(), number);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.printf("Thread %s - Task %d canceled.%n", Thread.currentThread().getName(), number);
return -1;
}
System.out.printf("Thread %s - Task %d finished.%n", Thread.currentThread().getName(), number);
return ThreadLocalRandom.current().nextInt(100);
}
}
接下來,我們測試使用執行緒池開啟 100 個執行緒處理 1000 個任務需要多長時間。
package git.snippets.vt;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
/**
* @author <a href="mailto:[email protected]">Grey</a>
* @date 2022/9/21
* @since 19
*/
public class App {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executor = Executors.newFixedThreadPool(100);
List<Task> tasks = new ArrayList<>();
for (int i = 0; i < 1_000; i++) {
tasks.add(new Task(i));
}
long time = System.currentTimeMillis();
List<Future<Integer>> futures = executor.invokeAll(tasks);
long sum = 0;
for (Future<Integer> future : futures) {
sum += future.get();
}
time = System.currentTimeMillis() - time;
System.out.println("sum = " + sum + "; time = " + time + " ms");
executor.shutdown();
}
}
執行結果如下
Thread pool-1-thread-1 - Task 0 waiting...
Thread pool-1-thread-3 - Task 2 waiting...
Thread pool-1-thread-2 - Task 1 waiting...
……
sum = 49879; time = 10142 ms
接下來,我們用虛擬執行緒測試整個事情。因此,我們只需要替換這一行
ExecutorService executor = Executors.newFixedThreadPool(100);
替換為
ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
執行效果如下
Thread - Task 0 waiting...
Thread - Task 2 waiting...
Thread - Task 3 waiting...
……
sum = 48348; time = 1125 ms
1125 ms
VS 10142 ms
,效能提升非常明顯。
注:本範例需要在 JDK 19 下執行,且需要增加 --enable-preview
引數,在 IDEA 下,這個引數設定如下
我們已經瞭解了建立虛擬執行緒的一種方法:使用Executors.newVirtualThreadPerTaskExecutor()
建立的執行器服務,為每個任務建立一個新的虛擬執行緒。
使用Thread.startVirtualThread()
或Thread.ofVirtual().start()
,我們也可以明確地啟動虛擬執行緒。
Thread.startVirtualThread(() -> {
// code to run in thread
});
Thread.ofVirtual().start(() -> {
// code to run in thread
});
特別說明:Thread.ofVirtual()
返回一個VirtualThreadBuilder
,其start()
方法啟動一個虛擬執行緒。另一個方法Thread.ofPlatform()
返回一個PlatformThreadBuilder
,通過它我們可以啟動一個平臺執行緒。
這兩種構造方法都實現了Thread.Builder
介面。這使得我們可以編寫靈活的程式碼,在執行時決定它應該在虛擬執行緒還是平臺執行緒中執行。
本文來自部落格園,作者:Grey Zeng,轉載請註明原文連結:https://www.cnblogs.com/greyzeng/p/16732227.html