Java19为Java平台带来了虚拟线程的第一个预览,这是OpenJDKs Project Loom的主要可交付成果,这是很长一段时间以来Java发生的最大变化之一——同时也是几乎无法察觉的变化。

虚拟线程从根本上改变了Java运行时与底层操作系统的交互方式,消除了可伸缩性的重大障碍——但对于我们如何构建和维护并发程序的改变相对较小。新的 API 表面几乎为零,虚拟线程的行为几乎与我们已知的线程完全相同。

虚拟线程

虚拟线程是Java.lang.Thread 的一种替代实现,它将它们的堆栈帧存储在Java垃圾收集堆中,而不是操作系统分配的单片内存块中。我们不必猜测一个线程可能需要多少堆栈空间,或者对所有线程进行一刀切的估计;虚拟线程的内存占用开始时只有几百字节,并随着调用堆栈的扩展和收缩而自动扩展和收缩。

  操作系统只知道平台线程,它仍然是调度单元。为了在虚拟线程中运行代码,Java运行时通过将其安装在某个平台线程(称为载体线程)上来安排它运行。挂载虚拟线程意味着将所需的堆栈帧从堆中临时复制到载体线程的堆栈中,并在挂载时借用载体堆栈。

当在虚拟线程中运行的代码会因 IO、锁定或其他资源可用性而阻塞时,它可以从载体线程中卸载,并且复制的任何修改的堆栈帧都将返回到堆中,从而释放载体线程以进行其他操作(例如就像运行另一个虚拟线程一样。)JDK 中几乎所有的阻塞点都已经过调整,因此当在虚拟线程上遇到阻塞操作时,虚拟线程会从其载体上卸载而不是阻塞。

  

在载体线程上挂载和卸载虚拟线程是Java代码完全不可见的实现细节。Java代码无法观察到当前载体的身份(调用Thread::currentThread总是返回虚拟线程);承载线程的 ThreadLocal 值对已挂载的虚拟线程不可见;载体的堆栈帧不会出现在虚拟线程的异常或线程转储中。在虚拟线程的生命周期中,它可能在许多不同的载体线程上运行,但是任何取决于线程标识的东西,例如锁定,都会看到它在哪个线程上运行的一致画面。

虚拟线程之所以如此命名,是因为它们与虚拟内存共享特性。使用虚拟内存,应用程序会产生一种错觉,即他们可以访问整个内存地址空间,而不受可用物理内存的限制。硬件通过根据需要将丰富的虚拟内存临时映射到稀缺的物理内存来完成这种错觉,当其他一些虚拟页面需要该物理内存时,旧的内容首先被分页到磁盘。同样,虚拟线程既便宜又丰富,根据需要共享稀缺和昂贵的平台线程,不活动的虚拟线程堆栈被“分页”到堆中。

虚拟线程具有相对较少的新 API 表面。有几种创建虚拟线程的新方法(例如,Thread::ofVirtual),但创建后,它们是普通的 Thread 对象,并且表现得像我们已经知道的线程。现有的 API,如 Thread::currentThread、ThreadLocal、中断、堆栈遍历等,在虚拟线程上的工作方式与在平台线程上的工作方式完全相同,这意味着我们可以自信地在虚拟线程上运行现有代码。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信小程序

微信扫一扫体验

立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部