写点什么

Java SE 19 虚拟线程

作者:钟奕礼
  • 2022 年 9 月 27 日
    湖南
  • 本文字数:2101 字

    阅读完需:约 7 分钟

说明

虚拟线程(Virtual Threads)是在 Project Loom 中开发的,并从 Java SE 19 开始作为预览功能引入 JDK。

在线程模型下, 一个 Java 线程相当于一个操作系统线程 ,而这些线程是很消耗资源的,如果启动的线程过多,会给整个系统的稳定性带来风险。

虚拟线程解决了这个问题,从 Java 代码的角度来看,虚拟线程感觉就像普通的线程,但它们不是 1:1 地映射到操作系统线程上。

有一个所谓的载体线程池,一个虚拟线程被临时映射到该池中。一旦虚拟线程遇到阻塞操作,该虚拟线程就会从载体线程中移除,而载体线程可以执行另一个虚拟线程(新的或之前被阻塞的)。

所以阻塞的操作不再阻塞执行的线程。这使得我们可以用一个小的载体线程池来并行处理大量的请求。

示例

场景

启动 1000 个任务,每个任务等待一秒钟(模拟访问外部 API),然后返回一个结果(在这个例子中是一个随机数)。

任务类如下

package git.snippets.vt;
import java.util.concurrent.Callable;import java.util.concurrent.ThreadLocalRandom;
/** * @author <a href="mailto:410486047@qq.com">Grey</a> * @date 2022/9/21 * @since 19 */public class Task implements Callable<Integer> {
private final int number;
public Task(int number) { this.number = number; }
@Override public Integer call() { System.out.printf("Thread %s - Task %d waiting...%n", Thread.currentThread().getName(), number); try { Thread.sleep(1000); } catch (InterruptedException e) { System.out.printf("Thread %s - Task %d canceled.%n", Thread.currentThread().getName(), number); return -1; } System.out.printf("Thread %s - Task %d finished.%n", Thread.currentThread().getName(), number); return ThreadLocalRandom.current().nextInt(100); }}
复制代码

接下来,我们测试使用线程池开启 100 个线程处理 1000 个任务需要多长时间。

package git.snippets.vt;
import java.util.ArrayList;import java.util.List;import java.util.concurrent.ExecutionException;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Future;
/** * @author <a href="mailto:410486047@qq.com">Grey</a> * @date 2022/9/21 * @since 19 */public class App { public static void main(String[] args) throws ExecutionException, InterruptedException { ExecutorService executor = Executors.newFixedThreadPool(100); List<Task> tasks = new ArrayList<>(); for (int i = 0; i < 1_000; i++) { tasks.add(new Task(i)); } long time = System.currentTimeMillis(); List<Future<Integer>> futures = executor.invokeAll(tasks); long sum = 0; for (Future<Integer> future : futures) { sum += future.get(); } time = System.currentTimeMillis() - time; System.out.println("sum = " + sum + "; time = " + time + " ms"); executor.shutdown(); }}
复制代码

运行结果如下

Thread pool-1-thread-1 - Task 0 waiting...Thread pool-1-thread-3 - Task 2 waiting...Thread pool-1-thread-2 - Task 1 waiting...……sum = 49879; time = 10142 ms
复制代码

接下来,我们用虚拟线程测试整个事情。因此,我们只需要替换这一行

ExecutorService executor = Executors.newFixedThreadPool(100);
复制代码

替换为

ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
复制代码

执行效果如下

Thread  - Task 0 waiting...Thread  - Task 2 waiting...Thread  - Task 3 waiting...……sum = 48348; time = 1125 ms
复制代码

1125 ms VS 10142 ms ,性能提升非常明显。

注:本示例需要在 JDK 19 下运行,且需要增加 --enable-preview 参数,在 IDEA 下,这个参数配置如下




我们已经了解了创建虚拟线程的一种方法:使用 Executors.newVirtualThreadPerTaskExecutor() 创建的执行器服务,为每个任务创建一个新的虚拟线程。

使用 Thread.startVirtualThread() 或 Thread.ofVirtual().start() ,我们也可以明确地启动虚拟线程。

Thread.startVirtualThread(() -> {  // code to run in thread});
Thread.ofVirtual().start(() -> { // code to run in thread});
复制代码

特别说明: Thread.ofVirtual() 返回一个 VirtualThreadBuilder ,其 start() 方法启动一个虚拟线程。另一个方法 Thread.ofPlatform() 返回一个 PlatformThreadBuilder ,通过它我们可以启动一个平台线程。

这两种构造方法都实现了 Thread.Builder 接口。这使得我们可以编写灵活的代码,在运行时决定它应该在虚拟线程还是平台线程中运行。

源码

hello-virtual-thread

参考资料

Java 19 Features (with Examples)

JDK 19 Release Notes

Virtual Threads in Java (Project Loom)

用户头像

钟奕礼

关注

还未添加个人签名 2021.03.24 加入

还未添加个人简介

评论

发布
暂无评论
Java SE 19 虚拟线程_java;_钟奕礼_InfoQ写作社区