Java SE 19 虛擬線程

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 下,這個參數配置如下

image

image

我們已經瞭解了創建虛擬線程的一種方法:使用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)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章