🌺The Begin🌺点点关注,收藏不迷路🌺


📌 本文导读:本文将深入剖析Java中synchronized轻量级锁的自旋机制,从源码层面解析锁膨胀过程,帮助你彻底理解轻量级锁何时自旋、如何自旋以及何时升级为重量级锁。全文包含四大核心章节3个彩色流程图5个源码片段。预计阅读时间22分钟


一、🔴 核心答案:轻量级锁会自旋

是的,Java的synchronized轻量级锁在面临竞争时会进行自旋。

轻量级锁在加锁过程中,如果CAS操作失败,意味着存在锁竞争,线程会先尝试自旋(忙循环等待)来获取锁,而不是立即挂起线程。自旋是轻量级锁向重量级锁膨胀前的最后一道防线,目的是避免线程进入阻塞状态所带来的用户态/内核态切换开销。

📌 一句话理解:轻量级锁通过CAS加锁;当CAS失败时,线程会自旋重试;自旋超过一定次数仍未成功,锁就膨胀为重量级锁。


二、🔵 流程图:轻量级锁加锁与自旋全过程

CAS成功

CAS失败

是-重入

否-有竞争

成功

失败且达到自旋阈值

📨 线程尝试获取轻量级锁

🟢 复制对象头Mark Word
到线程栈Lock Record

🟡 CAS操作
将Mark Word指向Lock Record

✅ 获得轻量级锁
锁标志位 00

检查Mark Word
是否指向当前线程?

🔴 准备锁膨胀

🟣 开始自旋
循环尝试获取锁

自旋期间
是否抢到锁?

❌ 升级为重量级锁
线程阻塞挂起


三、🟣 源码层面:自旋发生在哪里?

3.1 🟤 轻量级锁加锁入口

轻量级锁的加锁逻辑在ObjectSynchronizer::slow_enter方法中。当偏向锁加锁失败(快速加锁阶段失败)后,JVM会进入慢速加锁过程,即轻量级锁加锁。

// 🔴 轻量级锁加锁核心逻辑(源码示意)
void ObjectSynchronizer::slow_enter(Handle obj, BasicLock* lock, TRAPS) {
    // 复制对象头Mark Word到线程栈的Lock Record
    markOop mark = obj->mark();
    // CAS操作:尝试将Mark Word指向Lock Record
    if (mark->is_neutral()) {
        lock->set_displaced_header(mark);
        if (mark == obj()->cas_set_mark((markOop) lock, mark)) {
            // ✅ CAS成功,获得轻量级锁
            return;
        }
    }
    // ❌ CAS失败,进入锁膨胀流程(自旋+升级)
    // ...
}

3.2 🟠 自旋的实现:ObjectMonitor::enter()

当轻量级锁CAS失败后,JVM会调用ObjectSynchronizer::inflate()获取ObjectMonitor对象,然后执行ObjectMonitor::enter()进行自旋加锁。

// 🟡 ObjectMonitor::enter()中的自旋逻辑(源码示意)
void ObjectMonitor::enter(TRAPS) {
    // 1. 检查是否为重入
    if (TryLock(Self) > 0) {
        return;  // 重入成功
    }
    
    // 2. 自旋锁阶段
    if (TrySpin(Self) > 0) {
        return;  // 自旋成功获取锁
    }
    
    // 3. 自旋失败,进入重量级锁的阻塞队列
    // ... 线程挂起
}

3.3 🟡 两种自旋机制

根据OpenJDK源码和社区分析,自旋分为两种:

自旋类型 特点 说明
固定次数自旋 自旋次数固定(默认10次) 通过-XX:PreBlockSpin可调整
自适应自旋(JDK 1.6+) 根据上次自旋结果动态调整 上次成功→增加次数;失败→减少或跳过

关于自适应自旋的详细原理,可以参考OpenJDK社区的相关讨论:自适应自旋会根据前一次在同一个锁上的自旋结果动态调整,如果自旋成功,JVM会认为这次也很有希望,从而允许自旋更长时间;反之,则可能直接跳过自旋。


四、🟤 流程图:自旋在锁升级中的位置

第一个线程获取

其他线程竞争

CAS失败

自旋成功

自旋超时

🔵 无锁状态

🟢 偏向锁
记录线程ID

🟡 轻量级锁
CAS加锁

🟣 自旋
固定次数+自适应

🔴 重量级锁
线程阻塞


五、🟢 为什么需要自旋?——性能考量

5.1 🟠 自旋的价值

线程的阻塞和唤醒需要CPU从用户态切换到内核态,开销极大。如果锁的持有时间很短,让等待线程原地自旋(循环检查锁是否释放)比直接挂起更高效。

5.2 🟡 自旋的代价

自旋会占用CPU资源,如果锁被长时间占用,自旋的线程只会白白消耗CPU而无实际产出。因此自旋次数需要有限度,超过阈值后就应升级为重量级锁。


六、🔴 避坑指南

序号 要点 说明
自旋不是独立的锁状态 自旋是轻量级锁在竞争时的一种策略,而非独立的锁状态
自适应自旋是默认开启的 JDK 1.6+默认开启,无需手动配置
自旋次数可通过JVM参数调整 -XX:PreBlockSpin=10可设置固定自旋次数
高竞争场景自旋反而降低性能 竞争激烈时,应升级为重量级锁避免CPU空转

七、🔵 总结

synchronized轻量级锁确实会进行自旋。自旋发生在轻量级锁CAS失败后、升级为重量级锁之前,是避免线程阻塞的最后尝试。

三句话总结

  1. 轻量级锁:通过CAS加锁,无竞争时性能最优
  2. 自旋锁:CAS失败后,线程忙循环等待,避免立即阻塞
  3. 重量级锁:自旋超时或竞争加剧,线程挂起,依赖操作系统Mutex

在这里插入图片描述


🌺The End🌺点点关注,收藏不迷路🌺

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐