简介
在上一篇文章中,我们已经简单的介绍了JEP 425: Virtual Threads (Preview),以及如何使用虚拟线程编写例程,本文中将继续介绍虚拟线程的底层机制和核心源码。
虚拟线程的机制
调度模型
与Golang协程调度的GPM模型类似,JDK19中的虚拟线程也涉及类似的定义:
- VT:虚拟线程
- Platform Thread:平台线程,一个平台线程上可以运行很多虚拟线程
- OS Thread:操作系统线程,hotspot线程模型中,平台线程和OS线程关系为1:1。
平台线程和虚拟线程
除了早期的Green Thread方案,JVM中平台线程和OS线程都是1:1关系,因此平台线程限制颇多:
- 创建新线程需要调用pthread API,代价高昂。
- 受限于操作系统资源,平台线程数量不能过多。
- 线程过多时,大量CPU时间浪费在线程上下文切换,sys占用升高。
- 线程栈占用大量内存。
通过资源池,可以减少创建和销毁线程的代价,但上下文切换、数量限制和内存占用在平台线程框架内无法解决。
相比而言,虚拟线程优势相当明显:
- 虚拟线程仅在用户态即可创建,可以轻松创建上百万个。
- 虚拟线程的调度和切换,仅在用户态即可完成,没有内核上下文切换的开销。
- 虚拟线程栈内存占用小。
另一方面,虚拟线程也不适应如下场景:
- 阻塞操作时,如果VT没有主动释放执行权限,将阻塞整个平台线程。
- 计算机密集型,同样的如果没有主动触发调度,将会导致其他VT饿死。
核心代码
为了兼容现有的Java线程API体系,Thread新建了子类BaseVirtualThread、VirtualThread,帮助VT完全适配当前的JUC线程体系。
BaseVirtualThread
BaseVirtualThread是个抽象类,提供了三个抽象方法park、parkNanos、unpark。
sealed abstract class BaseVirtualThread extends Thread
permits VirtualThread, ThreadBuilders.BoundVirtualThread {
}
- sealed关键字是JDK15中引入的特性,定义BaseVirtualThread 为封闭类,只能被VirtualThread和 ThreadBuilders.BoundVirtualThread继承
- sealed的子类必须是final或者sealed类
VirtualThread
首先介绍VirtualThread几个核心的变量
// 需要执行的任务
private final Continuation cont;
// 执行任务包装类
private final Runnable runContinuation;
// 虚拟线程的状态
private volatile int state;
// park许可
private volatile boolean parkPermit;
// carrier线程,即虚拟线程绑定的平台线程
private volatile Thread carrierThread;
// 调度器
private final Executor scheduler;
private static final ForkJoinPool DEFAULT_SCHEDULER = createDefaultScheduler();
// 唤醒线程池
private static final ScheduledExecutorService UNPARKER = createDelayedTaskScheduler();
虚拟线程由JVM调度,JVM将VT分配给平台线程的动作称为挂载(mount),取消分配的动作称为卸载(unmount),线程状态如下:
// 初始状态
private static final int NEW = 0;
// 线程启动,由于虚拟线程的run()是个空方法,此时尚未开始执行任务
// 真正的任务执行在cont.run
private static final int STARTED = 1;
// 可执行,尚未分配平台线程
private static final int RUNNABLE</