使用Loom建立虛擬執行緒 - david

banq發表於2022-02-28

在這篇文章中,我們展示如何使用Loom實現類似Go語言的綠色虛擬執行緒。

Project loom 仍處於預覽階段,這意味著 api 可能隨時更改。如果您想自己嘗試這些示例,可使用Early-access build 19-loom+4-115 (2022/2/13) 製作的

 

引入虛擬執行緒

Java 中的執行緒只是由作業系統管理和排程的執行緒的一個小包裝器。Project Loom 向 Java 新增了一種稱為虛擬執行緒的新型執行緒,這些執行緒由 JVM 管理和排程。

要建立平臺執行緒(由作業系統管理的執行緒),您需要進行系統呼叫,而且這些呼叫成本很高。要建立虛擬執行緒,您不必進行任何系統呼叫,從而使這些執行緒在您需要時可以很便宜地製作。這些虛擬執行緒在載體執行緒上執行。在幕後,JVM 建立了一些平臺執行緒供虛擬執行緒執行。由於我們沒有系統呼叫和上下文切換,我們可以在幾個平臺執行緒上執行數千個虛擬執行緒。

 

建立虛擬執行緒

建立虛擬執行緒的最簡單方法是使用Thread類。使用 Loom,我們獲得了一個新的 builder 方法和 factory 方法來建立虛擬執行緒。

Runnable task = () -> System.out.println("Hello, world");

// Platform thread
(new Thread(task)).start();
Thread platformThread = new Thread(task);
platformThread.start();

// Virtual thread
Thread virtualThread = Thread.startVirtualThread(task);
Thread ofVirtualThread = Thread.ofVirtual().start(task);

// Virtual thread created with a factory
ThreadFactory factory = Thread.ofVirtual().factory();
Thread virtualThreadFromAFactory = factory.newThread(task);
virtualThreadFromAFactory.start();

這個例子首先向我們展示瞭如何建立一個平臺執行緒,接著是一個虛擬執行緒的例子。虛擬執行緒和平臺執行緒都以Runnable為引數,並返回一個執行緒的例項。此外,啟動一個虛擬執行緒與我們習慣於通過呼叫start()方法來啟動平臺執行緒是一樣的。

 

用Concurrency API建立虛擬執行緒

Loom還為Concurrency API新增了一個新的執行器來建立新的虛擬執行緒。新的VirtualThreadPerTaskExecutor返回一個實現ExecutorService介面的執行器,就像其他執行器那樣。讓我們先來看看使用Executors.newVirtualThreadPerTaskExecutor()方法來獲得一個使用虛擬執行緒的ExecutorService的例子。

Runnable task = () -> System.out.println("Hello, world");
ExecutorService executorService = Executors.newVirtualThreadPerTaskExecutor();
executorService.execute(task);

正如你所看到的,它看起來與現有的執行器並無不同。在這個例子中,我們使用Executors.newVirtualThreadPerTaskExecutor()來建立一個ExecutorService。這個虛擬執行緒執行器在一個新的虛擬執行緒上執行每個任務。由VirtualThreadPerTaskExecutor建立的執行緒的數量是沒有限制的。

 

我可以使用現有的執行緒執行器executors嗎?

簡短的回答是可以的,你可以通過給他們提供一個虛擬執行緒工廠來使用現有的執行器與虛擬執行緒。請記住,這些執行器是為了彙集執行緒而建立的,因為平臺執行緒的建立成本很高。使用一個彙集執行緒的執行器與虛擬執行緒結合起來可能是可行的,但這有點忽略了虛擬執行緒的意義。你不需要彙集它們,因為它們的建立成本很低。

ThreadFactory factory = Thread.ofVirtual().factory();
Executors.newVirtualThreadPerTaskExecutor();
Executors.newThreadPerTaskExecutor(factory); // Same as newVirtualThreadPerTaskExecutor
Executors.newSingleThreadExecutor(factory);
Executors.newCachedThreadPool(factory);
Executors.newFixedThreadPool(1, factory);
Executors.newScheduledThreadPool(1, factory);
Executors.newSingleThreadScheduledExecutor(factory);

在第一行,我們建立了一個虛擬執行緒工廠,它將處理執行器的執行緒建立問題。接下來,我們為每個執行器呼叫new方法,併為其提供我們剛剛建立的工廠。注意,用虛擬執行緒工廠呼叫newThreadPerTaskExecutor與直接呼叫newVirtualThreadPerTaskExecutor是一樣的。

 

Completable future

當我們使用CompletableFuture時,我們儘量在呼叫get之前將我們的動作串聯chain化,因為呼叫get會阻塞執行緒。有了虛擬執行緒,呼叫get就不會再阻塞執行緒了。沒有了使用get的懲罰,你可以隨時使用它,而不必寫非同步程式碼。這使得編寫和閱讀Java程式碼變得更加容易。

 

結構化的併發性

由於執行緒的建立成本很低,Project Loom還為Java帶來了結構化的併發性。通過結構化併發,你將執行緒的生命週期與一個程式碼塊繫結。在你的程式碼塊中,你建立你需要的執行緒,並在所有執行緒完成或停止時離開該程式碼塊。

System.out.println("---------");
try (ExecutorService e = Executors.newVirtualThreadPerTaskExecutor()) {
    e.submit(() -> System.out.println("1"));
    e.submit(() -> System.out.println("2"));
}
System.out.println("---------");

Try-with-resources語句可以使用ExecutorService,因為Project Loom用AutoCloseable介面擴充套件了Executor。在try中,我們提交所有需要完成的任務,一旦執行緒完成,我們就離開try。控制檯中的輸出將看起來像這樣。

---------
2
1
---------

第二條虛線將永遠不會被列印在數字之間,因為該執行緒在等待try-with-resources的完成。

 

總結

該專案仍處於預覽階段,在我們看到它投入生產之前,API可能會發生變化。但探索新的API,看看它已經給我們帶來了哪些效能上的改進,還是很不錯的。

 

相關文章