一、锁优化的底层基础:对象头 (Object Header)
每个 Java 对象在内存中分为三部分:
对象头 (Header):存储锁状态、GC 分代年龄等元数据。
实例数据 (Instance Data):对象的字段信息。
对齐填充 (Padding):保证对象大小是 8 字节的整数倍。
对象头的关键结构:
Mark Word:记录对象的锁状态和哈希码(32/64 位结构不同)。
Klass Pointer:指向类元数据的指针。
(如果是数组)数组长度。
Mark Word 的锁标记位决定了锁的当前状态(无锁、偏向锁、轻量级锁、重量级锁)。
二、锁升级过程:从无锁到重量级锁
JVM 会根据竞争情况逐步升级锁状态,避免直接使用重量级锁:
1. 偏向锁 (Biased Locking)
目标:消除无竞争场景下的同步开销。
原理:
第一个获取锁的线程会将线程 ID 写入 Mark Word,进入偏向模式。
后续如果没有其他线程竞争,持有偏向锁的线程无需 CAS 操作即可直接进入同步块。
适用场景:单线程重复访问同步代码。
升级条件:当其他线程尝试竞争偏向锁时,偏向锁会撤销并升级为轻量级锁。
2. 轻量级锁 (Lightweight Locking)
目标:减少多线程交替执行但无竞争时的锁开销。
原理:
线程在栈帧中创建锁记录(Lock Record),将 Mark Word 复制到其中。
使用 CAS 操作将 Mark Word 替换为指向锁记录的指针。
成功则获得锁;失败则自旋尝试,失败次数过多会升级为重量级锁。
适用场景:线程交替执行同步块,无实际竞争。
升级条件:自旋超过阈值(默认 10 次)或竞争激烈。
3. 重量级锁 (Heavyweight Locking)
原理:通过操作系统互斥量(Mutex Lock)实现,未抢到锁的线程会被阻塞,进入等待队列。
适用场景:高并发竞争场景。
开销:涉及用户态到内核态的切换,导致上下文切换和线程调度延迟。
三、其他关键优化技术
1. 自旋锁 (Spin Lock)
目标:减少线程阻塞的概率。
原理:线程在竞争轻量级锁失败时,不立即阻塞,而是循环(自旋)尝试获取锁。
自适应自旋(JDK 6+):JVM 根据前一次自旋的成功率动态调整自旋次数。
2. 锁消除 (Lock Elimination)
目标:移除不必要的同步操作。
原理:JIT 编译器通过逃逸分析(Escape Analysis),判断同步块内的对象是否仅被单个线程访问。若是,则直接消除锁。
示例:
public void method() {
Object lock = new Object(); // 局部变量,不会逃逸出方法
synchronized(lock) { // 锁会被 JVM 消除
// ...
}
}
3. 锁粗化 (Lock Coarsening)
目标:减少频繁加锁/解锁的开销。
原理:将多个连续的加锁操作合并为一个更大的锁范围。
示例:
for (int i = 0; i < 100; i++) {
synchronized(this) { // 循环内反复加锁会被粗化为外层一次加锁
// ...
}
}
四、锁优化的实践建议
减少锁粒度:缩小同步块范围(如使用 ConcurrentHashMap 的分段锁)。
避免锁竞争:优先使用无锁数据结构(如 CAS 操作、AtomicInteger)。
权衡锁策略:高并发场景下,可关闭偏向锁(-XX:-UseBiasedLocking)以减少撤销开销。
监控锁状态:通过 JVM 参数(如 -XX:+PrintFlagsFinal)或工具(JConsole、Arthas)分析锁竞争。
总结
Java 的 synchronized 锁通过 偏向锁 → 轻量级锁 → 重量级锁 的逐步升级策略,结合 锁消除、锁粗化、自适应自旋 等优化技术,在保证线程安全的同时,显著降低了同步开销。理解这些机制有助于编写高效并发代码和性能调优。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。