synchronized 为啥会让虚拟线程卸载失效
虚拟线程底层如何复用载体线程
虚拟线程实际上是一种封装,把runnable任务封装交给载体线程干活。但向上对程序员提供一套线程api,让程序员像以往使用传统线程一样去使用。
我们知道虚拟线程在底层复用载体线程干活,如果发生阻塞则执行代码(底层实际就是载体线程在执行)调用native方法,jvm就会把虚拟线程从载体线程上yield“卸载”,
(中断载体线程,然后把线程栈中涉及虚拟线程的部分复制到堆中进行保存,载体线程再被调度到其他任务)
载体线程可以去干其他活儿,io就绪结束阻塞之后,代码调用run则重新装载虚拟线程进行执行。(把之前堆中保存的信息恢复到当前线程栈)
synchronized带来的问题
在 Java 24 之前,当虚拟线程进入 synchronized 代码块时,JVM 会将锁的所有者记录为底层的载体线程(Carrier Thread),而不是虚拟线程本身。
这会导致一个问题:当虚拟线程在 synchronized 块内因 I/O 等操作而阻塞时,JVM 本来应该将它“卸载”下来,让载体线程去执行其他任务。但如果这样做,载体线程和虚拟线程的锁所有权关系就会变得混乱。
为了避免这种混乱,JVM 采取了一个简单粗暴的策略:只要虚拟线程在 synchronized 块内,就禁止将其从载体线程上卸载。虚拟线程因此被“钉”在载体线程上,即使它阻塞了,载体线程也无法去执行其他任务,这大大降低了虚拟线程的优势。
Java 24+ 的解决方案 (JEP 491)
为了解决这个问题,JEP 491 修改了 synchronized 的底层实现。其核心思路是让锁的所有权不再依赖于具体的载体线程,从而允许虚拟线程在持有锁的情况下也能被卸载。
1. 锁记录的转移:当虚拟线程被卸载(冻结)时,JVM 会将其持有的锁信息从载体线程的“锁栈”中复制出来,保存到虚拟线程的堆内存栈块(Stack Chunk)中。当虚拟线程被重新挂载(解冻)时,再将锁信息恢复回去。
2. 锁所有者的记录方式:对于重量级锁,JVM 不再记录持有锁的载体线程(JavaThread*),而是直接记录拥有该锁的 java.lang.Thread 的 ID(tid)。这样,锁的所有者就与具体的载体线程解耦了。
浙公网安备 33010602011771号