Java SE 19 虛擬執行緒

2022-09-26 21:01:48

Java SE 19 虛擬執行緒

作者:Grey

原文地址:

部落格園:Java SE 19 虛擬執行緒

CSDN:Java SE 19 虛擬執行緒

說明

虛擬執行緒(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介面。這使得我們可以編寫靈活的程式碼,在執行時決定它應該在虛擬執行緒還是平臺執行緒中執行。

原始碼

hello-virtual-thread

參考資料

Java 19 Features (with Examples)

JDK 19 Release Notes

Virtual Threads in Java (Project Loom)