说明
虚拟线程(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)
评论