【Java21 问答】为什么 synchronized 会 PINNED 虚拟线程?
Java21 正式发布了虚拟线程,但是留了一抹阴影——PINNED
参照官方文档有两种 pinned 的情况,笔者不太能理解为什么没有解决掉 synchronized 的场景,提一个比较幼稚的方案,在字节码加载的时候将 synchronized 替换为 ReentrantLock 是否就可以解决问题(玩笑)。
从功能上看 synchronized 关键字和 ReentrantLock 类提供了同样的锁机制,只是 synchronized 的锁机制实现在虚拟机运行时中(Java 是笔者所知的仅有的一个在语法层面提供锁能力的编程语言),而 ReentrantLock 实现在 Java 代码层面。而这次的虚拟线程功能主要也是实现在 Java 代码层面,只有必须在运行时提供的 Continuation 栈切换能力实现在 JVM 中。感觉 Java 团队在尽可能减少运行时的复杂度,在这个方向下 synchronized 可能确实不是一个高优解决的问题,Alan Bateman 在一个访谈中也提到了重新实现 Java Object Monitor 的想法。
虽然 Alan Bateman 一再强调这只影响 scalability,但是在一些特殊的场景了还是有死锁的风险,例如这篇文章中的例子。
最近 Alan Bateman 在 FOSDEM 2024 上又解释了一下这个事情,本文将尝试理解一下其中的技术细节。
synchronized 在对象的 Mark Word 中标记锁的状态(所以你才可以对任意对象加锁,而不是创建专门的锁对象):
001 无锁
101 偏向锁,高位是线程 id
x00 轻量锁,高位是栈帧中的锁记录地址
x10 重量锁,monitor 对象的地址
在 Java21 中还提供了一个新的轻量锁实现,当前默认的实现还是基于栈的,所以暂且不看。
在虚拟线程的场景偏向锁基本是失效的,另外偏向锁基本是个鸡肋功能,对稍有经验的开发者来说,是不会在单线程执行的场景写个 synchronized 关键字的,在 Java15 已经废弃了偏向锁,详见 JEP374。
轻量锁的主要问题是锁记录分配在栈上,在虚拟线程切换的时候,需要将栈帧拷贝到堆上,此时锁记录的地址就改变了。
重量锁的 monitor 对象是在堆上的,没有栈切换的问题,它的问题是 owner 字段是 JavaThread,也就是物理线程。
对症下药的解决方案也比较直观,感觉在 23 版本就能发布。
版权声明: 本文为 InfoQ 作者【袁世超】的原创文章。
原文链接:【http://xie.infoq.cn/article/0f6adcdeae135aef837804d55】。文章转载请联系作者。
评论