虚拟线程
JDK21引入虚拟线程时还有个pinning的问题,就是当虚拟线程在其载体上运行同步代码块时,它无法从载体上卸载。比如:
class CustomerCounter {
private final StoreRepository storeRepo;
private int customerCount;
CustomerCounter(StoreRepository storeRepo) {
this.storeRepo = storeRepo;
customerCount = 0;
}
synchronized void customerEnters() {
if (customerCount < storeRepo.fetchCapacity()) {
customerCount++;
}
}
synchronized void customerExits() {
customerCount--;
}
}
如果是单纯调用storeRepo.fetchCapacity()
则没问题,虚拟线程会从其载体(平台线程)卸载,释放平台线程给其他虚拟线程mount;但是如果是调用customerEnters()
,它用synchronized
修饰则JVM会将该虚拟线程pin住防止其被卸载,这样子的话虚拟线程与平台线程都会blocked,直到fetchCapacity()
方法返回。
之所以pinning是因为synchronized
依赖于monitors来确保它们只能由单个线程同时进入。在进入synchronized
块之前,线程必须获取与实例相关联的monitor
。JVM在平台线程级别跟踪这些monitor
的所有权,而不是在虚拟线程级别跟踪。 基于这些信息,假设不存在pinning,理论上,虚拟线程#1可以在synchronized块中间卸载,而虚拟线程#2可以装载到相同的平台线程上,并继续执行该synchronized块,因为承载线程是相同的,仍然持有对象的monitor。
从Java 24开始,虚拟线程可以获取、持有和释放监视器,而无需绑定到其载体线程。这意味着由于线程pinning而切换到不同的锁机制已不再是必需的。从现在起,无论是使用虚拟线程还是其他方法,性能表现都将相当一致。
在少数情况下,虚拟线程仍然会被pinning,其中一个情况是当它调用本地代码并返回到执行阻塞操作的Java代码时。在这种情况下,JDK Flight Recorder(JFR)会记录一个jdk.VirtualThreadPinned事件,如果要跟踪这些情况,可以启用JFR。
参考地址: