简介
经过多年的等待后,JEP 425: Virtual Threads (Preview)终于带来了虚拟线程,这一轻量级的线程模型对标其他语言中的协程,能够显著的减少编写、维护和观察高并发应用程序的工作量。
该特性的目标主要有:
- 支持服务端应用程序以thread-per-request样式编写,并最大限度压榨硬件性能。
- 兼容java.lang.Thread API,减少调用方代码改动。
- 兼容现有的JDK工具,用于故障排查、调整和分析。
该特性不会:
- 不会替代线程的传统实现,也不会静默升级现有的线程模型
- 改变Java的并发编程模型
- 在Java语言或Java库中提供新的数据并行结构,不会动摇Stream API的地位。
具体关于JEP 425信息,可以参考JEP 425 ,本文不再赘述。
例程
写一个简单的例子试试看,开n个虚拟线程,每个线程sleep 1秒后累加1次,以模拟IO密集型操作。
private static void runVirtualThread(int length) {
AtomicInteger ai = new AtomicInteger();
long start = System.currentTimeMillis();
try (ExecutorService es = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < length; i++) {
es.submit(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (ai.incrementAndGet() >= length) {
System.out.printf("duration=%d ms, done.%n", System.currentTimeMillis() - start);
}
});
}
}
}
然后再使用ScheduledExecutorService配合JMXBean打印JVM线程状态
ScheduledExecutorService se = Executors.newScheduledThreadPool(1);
se.scheduleAtFixedRate(() -> {
ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
ThreadInfo[] threadInfo = threadBean.dumpAllThreads(false, false);
System.out.printf("threadNumber=%d%n", threadInfo.length);
}, 100, 1000, TimeUnit.MILLISECONDS);
runVirtualThread(100000);
// runThread(100000);
Thread.sleep(100 * 1000);
se.shutdownNow();
再写一个使用传统操作系统线程的对比程序
private static void runThread(int length) {
AtomicInteger ai = new AtomicInteger();
long start = System.currentTimeMillis();
try (ExecutorService es = Executors.newCachedThreadPool()) {
for (int i = 0; i < length; i++) {
es.submit(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (ai.incrementAndGet() >= length) {
System.out.printf("duration=%d ms, done.%n", System.currentTimeMillis() - start);
}
});
}
}
}
IDE配置
由于虚拟线程在JDK19中属于preview特性,需要修改JVM参数才能使用,具体步骤如下:
- 修改编译参数
- 修改执行参数
执行
分别执行虚拟线程和操作系统线程,入参length为100,000,执行环境为Win11,Intel i5-12600KF。
- 使用虚拟线程时输出如下:
threadNumber=26
threadNumber=26
threadNumber=26
threadNumber=26
duration=3530 ms, done.
- 操作系统线程时输出如下:
threadNumber=2613
threadNumber=11072
threadNumber=15925
threadNumber=19725
threadNumber=22336
threadNumber=23933
duration=11614 ms, done.
结论
分别执行10次并删除坏点后,执行结果如下:
线程类型 | 执行时间(ms) | JVM线程数量 | 打开句柄数量 |
---|---|---|---|
虚拟线程 | 3,400 | 26 | 600 |
操作系统线程 | 11,500 | 23000 | 124,000 |
如上表所示,虚拟线程在执行IO密集型高并发任务时,性能远优于操作系统线程方式,对于操作系统资源的占用也降低很多,值得期待正式版。
虚拟线程的底层机制和其他改动,将在后文中继续分析。