导读:本期聚焦于小伙伴创作的《偏向锁在无竞争场景下如何通过对象头指向线程带来性能提升?》,敬请观看详情。对象头里的标记位若直接记录某个线程ID,该线程后续进入同步块便无需原子指令。偏向锁正是利用这一点,在无人争抢时把锁“偏向”首个持有者。一旦其他线程尝试获取,才触发撤销与升级。理解标记字结构、偏向延迟与批量重偏向机制,能帮助我们在高吞吐单线程反复加锁的场景下显著降低CAS开销,但也需警惕频繁竞争导致撤销震荡。

偏向锁在无竞争场景下如何通过对象头指向线程带来性能提升?

一、对象头与标记字的基础结构

要理解偏向锁的工作原理,首先要看 HotSpot 虚拟机中每个对象前部的两个关键部分:Mark WordKlass Pointer。Klass Pointer 指向方法区中的类元数据,而 Mark Word 则是一段长度与系统位数一致的动态比特位区域,其含义会随着锁状态的变化而改变。在无锁状态和偏向锁状态下,Mark Word 都需要保留对象的哈希码(如果已经计算过)、分代年龄等信息,但偏向锁还会额外占用若干位来存储持有锁的线程 ID 以及一个“是否偏向”的标志位。

以 64 位虚拟机且开启压缩指针为例,Mark Word 依然保持 64 位宽度。其中,偏向锁标志位为 1 且末尾锁标志位为01时,整个对象处于“可偏向”或“已偏向”状态。此时,大约有 54 位空间被用来存放 Java 层Thread对象的唯一标识。这意味着仅通过读取并比对 Mark Word 中这几十位数据,虚拟机就可以判断当前线程是否已经“拥有”该对象的偏向锁。下面的代码片段演示了用伪代码提取 Mark Word 并核对偏向线程的基本思路:

// 伪代码:读取对象标记字判断偏向状态
long mark = unsafe.getLong(obj, MARK_OFFSET);
boolean biased = (mark & BIASED_LOCK_PATTERN) == BIASED_LOCK_PATTERN;
if (biased) {
    long threadId = (mark >> THREAD_ID_SHIFT) & THREAD_ID_MASK;
    if (threadId == currentThreadId) {
        // 偏向命中,直接进入同步块
    } else {
        // 需要撤销偏向或触发重偏向
    }
}

这种设计使得首次获取锁的线程在后续执行中完全避开了 CPU 的原子指令(CAS)。CAS 操作在多核环境下会引起总线锁定或缓存锁,从而引入一致性消息流量,如果频繁执行代价相当可观。偏向锁将“每次获取锁”的成本简化为“首次写入 + 后续只读比较”,在特定负载下可以带来直接的性能红利。

二、无竞争场景下的性能红利来源

所谓“无竞争”,是指除了持有偏向的线程之外,再没有其他线程尝试进入同一个同步块。在该前提下,偏向锁避免了两项主要开销:一是每次进入和退出synchronized块时所需的原子指令,二是 HotSpot 解释器或 JIT 编译器在监视器 enter/exit 路径上的簿记工作。对于那些粒度极细但调用频率极高的同步操作,例如线程安全的日期格式化器、基于synchronized的局部计数器等,这种消除可以带来 10% 甚至 50% 以上的吞吐量提升。

考虑一个典型的计数器示例。如果关闭偏向锁(通过-XX:-UseBiasedLocking),每次执行synchronized方法都会走轻量级锁的路径,需要在线程栈帧中创建锁记录、用 CAS 替换 Mark Word、并在退出时恢复。而开启偏向锁后,只有第一次进入时会执行一次 CAS 将线程 ID 写入对象头,此后所有重复调用仅仅是比较 ID 是否一致,连轻量级锁的栈操作都省去了:

public class Counter {
    private int value = 0;

    // 单线程反复调用
    public synchronized void increment() {
        value++;
    }
}

// 测试:仅一个线程反复获取锁
for (int i = 0; i < 10_000_000; i++) {
    counter.increment();
}

从底层硬件角度看,轻量级锁即使没有线程争抢,也要发出带有排他语义的 CAS 指令,该指令会迫使处理器执行缓存一致性协议,严重时还会触发写缓冲区冲刷。偏向锁则让后续所有进入都由一条普通的加载指令完成,完全不会产生总线锁定或缓存行颠簸。因此,红利的多少很大程度上取决于“偏向线程持有锁的时间长度”与“不同线程争抢该锁的频次”之间的比值:比值越大,偏向锁的价值越突出。

三、偏向的延迟与撤销机制

HotSpot 并不是在程序启动的瞬间就给所有新对象开启偏向。JVM 预设了一个偏向延迟参数BiasedLockingStartupDelay,默认约 4 秒,目的是避免启动阶段大量类加载、初始化引发的多线程竞争导致偏向锁被频繁撤销。在延迟窗口内创建的对象都处于可偏向但未偏向的状态,等到延迟结束,首个获取锁的线程才会真正将 ID 写入对象头,完成“偏向”。

当另一个线程尝试进入已经偏向的同步块时,发现 Mark Word 中的线程 ID 并非自己,就会触发偏向撤销(revocation)。撤销的过程并不简单:它需要先将持有偏向的线程挂起到安全点(safepoint),遍历其栈帧检查是否仍然持有该锁,再根据结果决定是升级为轻量级锁还是执行重偏向。如果发现原持有线程已经退出同步块,虚拟机会尝试将偏向直接重指向当前线程(重偏向);如果原线程仍处于同步块内部,则必须升级为轻量级锁以保障正确性。下面的伪代码展示了这一决策分支:

// 伪代码:处理偏向锁竞争
if (owningThread == null || !owningThread.isAlive()) {
    // 原持有线程已消亡,可以直接重偏向给新线程
    mark = setThreadId(mark, currentThreadId);
} else if (owningThread.isSafepointSafe()) {
    // 原线程已退出同步块,执行重偏向
    rebiasToCurrentThread(obj);
} else {
    // 原线程仍持有锁,将偏向锁升级为轻量级锁
    revokeBiasedToLightweight(obj);
}

当系统中大量对象被一个线程偏向后,又被另一个线程接管,就会触发 JVM 内置的启发式策略:批量重偏向(bulk rebias)和批量撤销(bulk revoke)。虚拟机以类为单位统计偏向撤销次数,一旦超过阈值(由BiasedLockingBulkRebiasThresholdBiasedLockingBulkRevokeThreshold控制),就会对此后该类的所有新对象直接偏向到新线程,或者干脆彻底禁用该类的偏向锁。这说明偏向锁在“线程亲和性极强”的场景下收益最大,而在“同一对象被多个线程反复轮换使用”的模式下反而可能因撤销开销而拖累性能。

四、实践中的取舍与调优

在真实的分布式系统或微服务应用中,每个请求通常由不同的 Worker 线程处理,并可能触及一些共享的带同步的组件(例如连接池、缓存容器)。这种情况下,偏向锁往往弊大于利,因为频繁的撤销和升级会消耗额外的 CPU 时间,甚至引发安全点停顿。因而许多高并发 Web 框架、数据库连接池内部都会显式地通过 JVM 参数关闭偏向锁。反之,在单机计算、批处理任务、GUI 事件线程或者明确只由固定线程操作的场景中,开启偏向锁则能稳定降低同步开销。

如果我们想观测偏向锁的实际效果,可以借助以下 JVM 参数进行诊断和调优:

  • -XX:+PrintBiasedLockingStatistics:输出偏向锁的计数和撤销统计,帮助判断偏向是否频繁失效。
  • -XX:BiasedLockingStartupDelay=0:将偏向延迟设置为 0,适合短生命周期程序或压测时提前让偏向生效。
  • -XX:-UseBiasedLocking:在已知存在大量线程竞争的情况下,直接关闭偏向锁,避免撤销开销。
  • -XX:BiasedLockingBulkRebiasThreshold-XX:BiasedLockingBulkRevokeThreshold:调整批量重偏向和撤销的阈值,适用于对延迟极端敏感的特定类实例。

从软件架构的角度看,偏向锁只是一种“让不可避免的同步付出更小代价”的优化手段,而不是鼓励无节制地使用synchronized。在可行的情况下,通过无锁数据结构、不可变对象或线程封闭等方式彻底消除共享可变状态,始终是最可靠的性能保证。只有当设计上确实需要同步,且确认为长期单线程主导的访问模式时,偏向锁这种在对象头中“悄然粘附”线程 ID 的设计,才能帮助应用安静地收获它所承诺的性能红利。

biased_locking对象头无竞争锁优化修改时间:2026-08-02 22:11:02

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。