虚拟线程 - VirtualThread源码透视( 二 )


  • 可以大量创建,例如十万级别、百万级别,而不会占据大量内存
  • 由JVM进行调度和状态切换,并且与系统线程"松绑"
  • 用法与原来平台线程差不多,或者说尽量兼容平台线程现存的API
Loom项目中开发的虚拟线程就是为了解决这个问题,看起来它的运行示意图如下:
虚拟线程 - VirtualThread源码透视

文章插图
当然,平台线程不是简单地与虚拟线程进行1:N的绑定,后面的章节会深入分析虚拟线程的运行原理 。
虚拟线程实现原理虚拟线程是一种轻量级(用户模式)线程,这种线程是由Java虚拟机调度,而不是操作系统 。虚拟线程占用空间小,任务切换开销几乎可以忽略不计,因此可以极大量地创建和使用 。总体来看,虚拟线程实现如下:
virtual thread = continuation + scheduler虚拟线程会把任务(一般是java.lang.Runnable)包装到一个Continuation实例中:
  • 【虚拟线程 - VirtualThread源码透视】当任务需要阻塞挂起的时候,会调用Continuation的yield操作进行阻塞
  • 当任务需要解除阻塞继续执行的时候,Continuation会被继续执行
Scheduler也就是执行器,会把任务提交到一个载体线程池中执行:
  • 执行器是java.util.concurrent.Executor的子类
  • 虚拟线程框架提供了一个默认的ForkJoinPool用于执行虚拟线程任务
下文会把carrier thread称为"载体线程",指的是负责执行虚拟线程中任务的平台线程,或者说运行虚拟线程的平台线程称为它的载体线程
操作系统调度系统线程,而Java平台线程与系统线程一一映射,所以平台线程被操作系统调度,但是虚拟线程是由JVM调度 。JVM把虚拟线程分配给平台线程的操作称为mount(挂载),反过来取消分配平台线程的操作称为unmount(卸载):
  • mount操作:虚拟线程挂载到平台线程,虚拟线程中包装的Continuation栈数据帧或者引用栈数据会被拷贝到平台线程的线程栈,这是一个从堆复制到栈的过程
  • unmount操作:虚拟线程从平台线程卸载,大多数虚拟线程中包装的Continuation栈数据帧会留在堆内存中
这个mount -> run -> unmount过程用伪代码表示如下:
mount();try {    Continuation.run();} finally {    unmount();}从Java代码的角度来看,虚拟线程和它的载体线程暂时共享一个OS线程实例这个事实是不可见,因为虚拟线程的堆栈跟踪和线程本地变量与平台线程是完全隔离的 。JDK中专门是用了一个FIFO模式的ForkJoinPool作为虚拟线程的调度程序,从这个调度程序看虚拟线程任务的执行流程大致如下:
  • 调度器(线程池)中的平台线程等待处理任务

虚拟线程 - VirtualThread源码透视

文章插图
  • 一个虚拟线程被分配平台线程,该平台线程作为运载线程执行虚拟线程中的任务

虚拟线程 - VirtualThread源码透视

文章插图
  • 虚拟线程运行其Continuation,从而执行基于Runnable包装的用户任务

虚拟线程 - VirtualThread源码透视

文章插图