使用Loom创建虚拟线程 - david


在这篇文章中,我们展示如何使用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,看看它已经给我们带来了哪些性能上的改进,还是很不错的。