03、Java 20 新特性 - 虚拟线程(第二个预览版)

自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 的降级也已经成为常规功能。