自Java 诞生以来,线程一直是 Java 的一部分,自从 Loom 项目开始,我们渐渐开始将其称为“平台线程”。平台线程在底层操作系统线程上运行 Java 代码,并在代码的整个生命周期中捕获操作系统线程。因此,平台线程的数量受限于可用的操作系统线程数。
然而,现代应用程序可能需要比这更多的线程。例如,在同时处理成千上万个请求时。这就是虚拟线程的作用。虚拟线程是 java.lang.Thread 的实例,它在底层操作系统线程上运行 Java 代码,但不会在代码的整个生命周期中捕获操作系统线程。这意味着许多虚拟线程可以在同一个操作系统线程上运行它们的 Java 代码,有效地共享它。因此,虚拟线程的数量可以比可用的操作系统线程数量多得多。
虚拟线程不仅数量众多,而且创建和处理开销也很低。这意味着一个 Web 框架可以将一个新的虚拟线程专门用于处理请求的任务,并且仍然能够处理成千上万甚至数百万个请求。
典型用例
使用虚拟线程不需要学习新概念,但可能需要放弃开发用于应付现代高成本线程的习惯。虚拟线程不仅将帮助应用程序开发人员,而且还将帮助框架设计人员提供易于使用的 API,这些 API 与平台的设计兼容,而不会影响可扩展性。
创建虚拟线程
与平台线程一样,虚拟线程也是 java.lang.Thread
的一个实例。因此,可以像使用平台线程一样使用虚拟线程。创建虚拟线程与创建平台线程有所不同,但同样容易:
Response handle() throws IOException {
String theUser = findUser();
int theOrder = fetchOrder();
return new Response(theUser, theOrder);
}
当代码已经使用了 ExecutorService
接口时,切换到虚拟线程工作量甚至更少:
Response handle() throws ExecutionException, InterruptedException {
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Future < String > user = scope.fork(() - > findUser());
Future < Integer > order = scope.fork(() - > fetchOrder());
scope.join(); // Join both forks
scope.throwIfFailed(); // ... and propagate errors
// Here, both forks have succeeded, so compose their results
return new Response(user.resultNow(), order.resultNow());
}
}
注意:Java 19 中的 ExecutorService 接口进行了调整,扩展了 AutoCloseable。因此,现在可以在 try-with-resources 结构中使用它。
与 Java 19 有何不同
此功能处于“第二个预览版”阶段,以便获得更多反馈。除此之外,还有一些API更改已经成为常规功能,不再提供预览。这是因为它们涉及的功能通常很有用,不仅适用于虚拟线程,还包括:
Thread 类中的新方法:
- join(Duration);
- sleep(Duration);
- threadId().
Future 中的新方法(用于检查任务状态和结果)
ExecutorService
扩展 AutoCloseable
,以便可以在 try-with-resources
块中使用
除此之外,ThreadGroup 的降级也已经成为常规功能。