ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

轻量级锁失败后,到底会不会自旋?——一次被八股文带偏的复盘

轻量级锁失败后,到底会不会自旋?——一次被八股文带偏的复盘 轻量级锁失败后到底会不会自旋——一次被八股文带偏的复盘起因前几天复习 Java 锁升级看到一个问题轻量级锁获取失败后会发生什么我脑子里立刻蹦出那句经典的八股文答案“轻量级锁会先自旋尝试获取锁自旋失败后再升级为重量级锁。”看起来没毛病网上到处都是这个说法。但当我真正去翻 HotSpot 源码时发现事情并不是这样。源码里到底发生了什么以 JDK 8 的 HotSpot 为例轻量级锁的获取逻辑在fast_lock里核心步骤非常干脆CAS 尝试把对象头的 Mark Word 替换成指向当前线程栈中 Lock Record 的指针。成功拿到锁继续执行。失败直接调用ObjectSynchronizer::inflate膨胀成重量级锁。翻遍这条路径没有任何自旋逻辑。CAS 一旦失败就意味着“存在真实竞争”JVM 不再做无谓的尝试直接升级。也就是说轻量级锁 CAS 失败 → 直接膨胀为重量级锁中间没有自旋。那“自旋”到底在哪自旋确实存在但它属于重量级锁内部的优化不是轻量级锁的步骤。当锁已经膨胀成重量级锁ObjectMonitor后线程尝试获取 monitor 时如果发现锁被占用ObjectMonitor 的入口逻辑会先让线程短暂自旋TrySpin_VaryDuration自适应自旋希望能抢到刚刚被释放的锁避免直接park进入内核态阻塞。自旋一段时间仍失败才真正阻塞。所以正确的划分是阶段行为轻量级锁 CAS 失败直接膨胀为重量级锁不自旋重量级锁获取失败先自旋失败再阻塞八股文把这两件事揉在了一起说成“轻量级锁自旋失败再升级”顺序和归属都错了。为什么会流传这个错误说法我复盘了一下大概有几个原因“自旋锁”这个概念太深入人心。JDK 1.6 之前自旋锁是一个独立的优化-XX:UseSpinning很多人一提锁竞争就想到自旋于是想当然地安到了轻量级锁头上。描述上的偷懒。从线程视角看“我抢锁失败了等了一会儿然后被阻塞”——这个“等了一会儿”很容易被笼统地说成自旋而不去区分它发生在哪个阶段。面试八股文的自我复制。一个说法被写进博客、被面试题收录、被更多人背诵就慢慢变成了“标准答案”很少有人回去核对源码。一个补充新版本 JDK 的变化值得一提的是在较新的 JDK如 JDK 21 及以后中轻量级锁的实现有所调整。在LM_LIGHTWEIGHT模式下CAS 遇到竞争时可能会先短暂自旋一下目的是避免过早膨胀。但这是新架构下的性能微调不能拿来解释经典实现JDK 8 等的行为。所以如果你看的是 JDK 8 的源码结论就是失败即膨胀不自旋。教训这件事给我最大的提醒是八股文和源码事实之间经常有偏差。八股文适合快速回忆框架但一旦涉及“具体在哪一步、发生了什么”就必须回到源码。尤其是并发这种细节极多的领域一句笼统的“先自旋再升级”可能就把两个不同阶段的机制混为一谈。下次再看到类似的说法我的第一反应应该是这个行为发生在哪个阶段属于哪把锁的机制源码里真的有这一步吗而不是直接背下来。总结轻量级锁 CAS 失败 →直接膨胀为重量级锁不自旋。自旋发生在重量级锁的获取入口是 ObjectMonitor 的优化。八股文“轻量级锁自旋失败再升级”的说法混淆了阶段和归属。新版本 JDK 有调整但经典实现JDK 8就是“失败即膨胀”。本文是一次自我纠错的记录。如果你也在复习锁升级希望它能帮你少走一点弯路。
返回列表