JEP 425:通过虚拟线程提高吞吐量
JEP 425,即虚拟线程(预览版),已经从JDK 19的拟议状态晋升为 目标状态。这个JEP在Project Loom的保护伞下引入了虚拟线程。这些轻量级的线程旨在极大地减少编写、维护和观察Java平台上的高吞吐量并发应用的工作。这是一个预览功能。
Java是第一个将线程纳入核心语言作为其并发编程单元的主流编程平台。经典的Java线程(一个实例 [java.lang.Thread](https://download.java.net/java/early_access/loom/docs/api/java.base/java/lang/Thread.html))是对操作系统(OS)线程(也被称为平台线程)的一对一封装。另一方面,虚拟线程是JDK的轻量级实现。 **Thread**类的轻量级实现,由JDK而不是操作系统提供。虚拟线程使用平台线程来运行它们的代码,但它不会在代码的整个生命周期内捕获OS线程。这使得许多虚拟线程可以在同一个操作系统线程上运行他们的Java代码。
虚拟线程有很多好处。最重要的是,它使创建线程的成本降低,有助于提高应用程序的吞吐量。现在的服务器应用程序通过将每个请求委托给一个独立的执行单元,即一个平台线程来处理并发的用户请求。这种每请求一个线程的编程风格很容易理解,容易编程,也容易调试和剖析。然而,平台线程的数量是有限的,因为JDK将线程作为操作系统线程的包装物来实现。因此,当一个应用程序需要扩展以增加吞吐量时,它无法通过平台线程来实现。现在,由于大量的虚拟线程很容易创建,每请求线程的编程风格也随之缓解了这种可扩展性瓶颈。
此外,当运行在虚拟线程上的代码调用一个阻塞的I/O时,虚拟线程会被自动暂停,直到它可以在以后恢复。在这段时间里,其他虚拟线程可以接管操作系统线程并继续运行他们的代码。相反,在类似的情况下,操作系统线程会被阻塞,由于它们的限制,这并不可取。
虚拟线程支持线程局部变量、同步块、线程中断等。对Java开发者来说,这意味着虚拟线程仅仅是廉价而丰富的线程,编程方法完全没有改变。现有的针对经典线程编写的Java代码可以很容易地在虚拟线程中运行,而无需改变。
热衷于试验虚拟线程的开发者可以下载JDK 19的早期访问版本并熟悉它。该 **Thread**类提供了一个新方法。**[startVirtualThread(Runnable)](https://download.java.net/java/early_access/loom/docs/api/java.base/java/lang/Thread.html#startVirtualThread(java.lang.Runnable))**的方法,该方法接收一个类型为 **[Runnable](https://docs.oracle.com/javase/7/docs/api/java/lang/Runnable.html)**的参数并创建虚拟线程。请看下面的例子:
public class Main {
public static void main(String[] args) throws InterruptedException {
var vThread = Thread.startVirtualThread(() -> {
System.out.println("Hello from the virtual thread");
});
vThread.join();
}
}
由于这是一个预览功能,开发者需要提供 **--enable-preview**标志来编译这段代码,如以下命令所示:
javac --release 19 --enable-preview Main.java
运行程序也需要同样的标志:
java --enable-preview Main
然而,人们可以直接使用源代码启动器来运行。在这种情况下,命令行将是:
java --source 19 --enable-preview Main.java
jshell选项也是可用的,但也需要启用预览功能:
jshell --enable-preview
虽然 **Thread.startVirtualThread(Runnable)**是创建虚拟线程的便捷方式,新的API,如 **[Thread.Builder](https://download.java.net/java/early_access/loom/docs/api/java.base/java/lang/Thread.Builder.html)**, **[Thread.ofVirtual()](https://download.java.net/java/early_access/loom/docs/api/java.base/java/lang/Thread.html#ofVirtual())**,和 **[Thread.ofPlatform()](https://download.java.net/java/early_access/loom/docs/api/java.base/java/lang/Thread.html#ofPlatform())**被添加到创建虚拟和平台线程。
虚拟线程不能使用公共构造函数来创建。它作为一个守护线程运行,并具有 **[NORM_PRIORITY](https://download.java.net/java/early_access/loom/docs/api/java.base/java/lang/Thread.html#NORM_PRIORITY)**.因此,的 **[Thread.setPriority(int)](https://download.java.net/java/early_access/loom/docs/api/java.base/java/lang/Thread.html#setPriority(int))**和 **[Thread.setDaemon(boolean)](https://download.java.net/java/early_access/loom/docs/api/java.base/java/lang/Thread.html#setDaemon(boolean))**方法不能改变虚拟线程的优先级,也不能把它变成一个非守护线程。另外,活动线程携带虚拟线程,所以它们不能成为任何 **ThreadGroup**.的用法是 **Thread.getThreadGroup()**返回 "VirtualThreads"。
虚拟线程永远不应该被池化,因为每个线程在其生命周期内只打算运行一个任务。相反,该模型是无限制地创建虚拟线程。为此,我们添加了一个无约束的执行器。它可以通过一个新的工厂方法访问 **Executors.newVirtualThreadPerTaskExecutor()**.这些方法能够实现与现有的使用线程池的代码的迁移和互操作性。 **ExecutorService**.考虑一下下面的例子:
import java.time.Duration;
import java.util.concurrent.Executors;
import java.util.stream.IntStream;
public class Main {
public static void main(String[] args) {
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
IntStream.range(0, 10_000).forEach(i -> {
executor.submit(() -> {
Thread.sleep(Duration.ofSeconds(1));
return i;
});
});
}
}
}
虚线程的默认调度器是在 "虚拟 "中引入的偷工减料的调度器。 [ForkJoinPool](https://docs.oracle.com/en/java/javase/18/docs/api/java.base/java/util/concurrent/ForkJoinPool.html).