一、一句话描述虚拟线程
虚拟线程是一个新的轻量级 java.lang.Thread 变体,不由操作系统管理或调度,由 JVM 负责调度。JVM通过复用、管理、调用系统平台线程达到轻量线程的目的。
二、使用虚拟线程池例子
private static void testVirtualThread() {
var a = new AtomicInteger(0);
// 创建一个固定200个线程的线程池
try (var vs = Executors.newVirtualThreadPerTaskExecutor()) {
List<Future<Integer>> futures = new ArrayList<>();
var begin = System.currentTimeMillis();
// 向线程池提交1000个sleep 1s的任务
for (int i = 0; i < 1_000; i++) {
var future = vs.submit(() -> {
Thread.sleep(Duration.ofSeconds(1));
return a.addAndGet(1);
});
futures.add(future);
}
// 获取这1000个任务的结果
for (var future : futures) {
var i = future.get();
if (i % 100 == 0) {
System.out.print(i + " ");
}
}
// 打印总耗时
System.out.println("Exec finished.");
System.out.printf("Exec time: %dms.%n", System.currentTimeMillis() - begin);
} catch (ExecutionException | InterruptedException e) {
e.printStackTrace();
}
}
这段代码逻辑很简单,创建一个虚拟线程池,并向虚拟线程池中体检1000个任务,每个任务睡1秒钟并打印最终执行时间。
当把创建虚拟线程池的方法改成传统线程池的代码后,我们可以得到传统方法,具体如下
private static void testThread() {
var a = new AtomicInteger(0);
// 创建一个固定200个线程的线程池
try (var vs = Executors.newFixedThreadPool(200)) {
List<Future<Integer>> futures = new ArrayList<>();
var begin = System.currentTimeMillis();
// 向线程池提交1000个sleep 1s的任务
for (int i = 0; i < 1_000; i++) {
var future = vs.submit(() -> {
Thread.sleep(Duration.ofSeconds(1));
return a.addAndGet(1);
});
futures.add(future);
}
// 获取这1000个任务的结果
for (var future : futures) {
var i = future.get();
if (i % 100 == 0) {
System.out.print(i + " ");
}
}
// 打印总耗时
System.out.println("Exec finished.");
System.out.printf("Exec time: %dms.%n", System.currentTimeMillis() - begin);
} catch (ExecutionException | InterruptedException e) {
e.printStackTrace();
}
}
运行这两段代码,分别得到两段代码的耗时,耗时如下
虚拟线程耗时:1026ms
平台线程耗时:5094ms
通过结果看出使用虚拟线程池可以提升性能约500%。
三、虚拟线程作用
区别于虚拟线程,传统的线程对象叫做平台线程(platform thread)。平台线程在底层 OS 线程上运行 Java 代码,并在代码的整个生命周期中占用该 OS 线程,因此平台线程的数量受限于 OS 线程的数量。虚拟线程是 java.lang.Thread 的一个实例,它在底层 OS 线程上运行 Java 代码,但不会在代码的整个生命周期中占用该 OS 线程。也就是说,多个虚拟线程可以在同一个 OS 线程上运行其 Java 代码,可以有效地共享该线程。平台线程独占宝贵的 OS 线程,而虚拟线程则不会,因此虚拟线程的数量可以比 OS 线程的数量多得多,执行阻塞任务的整体吞吐量也就大了很多。虚拟线程虽然可以带来更大的吞吐量,但并不能让单个任务计算得更快。虚拟线程可以发挥的最大作用是,可以让采用单请求单线程(thread-per-request)的方式编写的服务器程序最大化地利用CPU计算资源 。 其原因在于服务器程序有两大特点,一是需要处理较大吞吐量的请求,二是请求处理的过程大多是由IO密集型逻辑组成,这就导致采用平台线程实现的单请求单线程编写方式,可能会有大量的IO阻塞占据了平台线程资源,从而不能充分利用CPU资源,使用虚拟线程带来的服务可用性提升会非常明显。