synchronized底层原理深入剖析
synchronized
关键字是实现线程同步的核心机制,其底层原理涉及对象头结构、Monitor监视器、锁升级策略以及JVM优化策略。
一、对象头与锁状态存储
每个Java对象在内存中由对象头(Header)、实例数据和对齐填充组成,锁信息存储在对象头的Mark Word字段中。
Mark Word结构(64位JVM为例):
锁状态 | 存储内容(64位) |
---|---|
无锁 | 哈希码(31位)、分代年龄(4位)、锁标志位(01) |
偏向锁 | 线程ID(54位)、Epoch(2位)、锁标志位(01) |
轻量级锁 | 指向栈中锁记录的指针(62位)、锁标志位(00) |
重量级锁 | 指向Monitor的指针(62位)、锁标志位(10) |
- 锁标志位:标识当前锁状态(01/00/10),决定Mark Word的解析方式。
二、Monitor监视器机制
每个对象关联一个Monitor(由C++实现的ObjectMonitor
结构),其核心组成:
- Owner:持有锁的线程,初始为
NULL
。 - EntryList:竞争锁失败的线程队列(阻塞态)。
- WaitSet:调用
wait()
后释放锁的线程队列(等待唤醒)。
工作流程:
- 线程通过
monitorenter
指令尝试获取锁:- 若Owner为空,CAS设置Owner为当前线程,
_recursions
计数器置1(支持重入)。 - 若Owner是当前线程,
_recursions
计数器递增。 - 竞争失败则进入EntryList等待。
- 若Owner为空,CAS设置Owner为当前线程,
- 释放锁时执行
monitorexit
,递减计数器直至0,唤醒EntryList中的线程。
三、锁升级策略(自适应优化)
JVM根据线程竞争动态调整锁状态,降低同步开销:
-
偏向锁(Biased Locking)
- 适用场景:单线程重复获取锁。
- 实现:通过CAS将线程ID写入Mark Word,后续无需同步操作。
- 撤销:检测到其他线程竞争时(通过Epoch机制),升级为轻量级锁。
-
轻量级锁(Lightweight Locking)
- 适用场景:低竞争、短临界区。
- 实现:线程在栈帧中创建Lock Record,CAS将Mark Word复制到Lock Record并指向它。
- 自旋失败:超过默认自旋次数(JDK6前固定10次,后改为自适应),升级为重量级锁。
-
重量级锁(Heavyweight Locking)
- 实现:依赖操作系统的互斥量(Mutex)实现线程阻塞,通过Monitor的EntryList管理竞争线程。
- 开销:涉及用户态到内核态的切换,性能损耗较高。
四、synchronized的字节码实现
通过monitorenter
和monitorexit
指令实现同步控制:
public void syncMethod() {
synchronized(obj) {
// 临界区代码
}
}
编译后的字节码:
0: aload_1 // 加载obj到操作数栈
1: dup
2: astore_2
3: monitorenter // 尝试获取锁
4: aload_2
5: monitorexit // 正常退出释放锁
6: goto 14
9: astore_3
10: aload_2
11: monitorexit // 异常退出释放锁(确保锁释放)
12: aload_3
13: athrow
14: return
- 异常处理表:保证无论代码块是否抛出异常,都会执行
monitorexit
。
五、JVM优化策略
-
锁粗化(Lock Coarsening)
合并多个相邻同步块,减少锁获取/释放次数。// 优化前 synchronized(obj) { ... } synchronized(obj) { ... } // 优化后 synchronized(obj) { ...; ... }
-
锁消除(Lock Elimination)
通过逃逸分析,移除不可能存在共享资源竞争的锁。public String concat(String s1, String s2) { StringBuffer sb = new StringBuffer(); // 局部变量,无竞争 sb.append(s1); sb.append(s2); return sb.toString(); }
-
自适应自旋(Adaptive Spinning)
根据历史自旋成功率动态调整自旋次数,避免CPU空转。
六、性能影响与应用场景
- 低竞争场景:偏向锁和轻量级锁几乎无性能损耗。
- 高竞争场景:重量级锁的线程阻塞可能成为性能瓶颈(建议改用
ReentrantLock
或CAS)。 - 锁粒度控制:尽量缩小同步代码块范围,减少锁持有时间。
总结
synchronized
通过对象头存储锁状态、Monitor实现互斥访问、锁升级策略平衡性能,成为Java线程安全的基石。其设计体现了JVM对“快速路径(Fast Path)”和“慢速路径(Slow Path)”的智能切换。开发中需结合竞争强度、临界区大小等因素,合理选择同步方案。
synchronized锁对象为class的时候锁信息储存在哪里?
当使用 synchronized
锁对象为 Class
时(例如 synchronized(MyClass.class)
或修饰静态方法),锁信息存储的位置与普通对象锁的存储机制一致,但其具体载体是 Class 类对应的 Java 对象头。
Monitor 机制
Monitor 机制是 synchronized 关键字实现线程同步的核心底层机制,其工作原理可分为以下几个关键点:
一、Monitor 的基本结构
-
对象关联性
每个 Java 对象(包括类对象)在创建时都会关联一个 Monitor(监视器),用于管理线程对同步代码的访问。 -
核心组成部分
- Owner(持有者):当前持有锁的线程。
- Entry Set(入口队列):等待获取锁的线程队列。
- Wait Set(等待队列):通过
wait()
方法主动释放锁并进入阻塞状态的线程队列。
二、互斥访问的实现原理
-
锁的获取与释放
- 当线程执行到
synchronized
代码块时,会尝试通过 CAS 操作获取对象的 Monitor。 - 若 Monitor 的 Owner 为空,线程成为 Owner 并执行同步代码;否则,线程进入 Entry Set 等待。
- 当线程执行到
-
重入性支持
Monitor 通过计数器(_recursions
)支持锁重入,同一线程多次获取锁时计数器递增,释放时递减至零才会完全释放锁。
三、线程协作机制(wait/notify)
-
wait()
的运作
线程调用wait()
后,会释放锁并进入 Wait Set,等待其他线程通过notify()
或notifyAll()
唤醒。 -
notify()
的触发
唤醒 Wait Set 中的一个线程,该线程会从阻塞状态转移到 Entry Set,重新竞争锁。
object和锁相关的属性方法
与锁相关的核心机制通过 Object
类的方法和底层对象结构实现,主要涉及线程同步、锁状态管理及线程协作。以下是详细说明:
一、Object 类中与锁相关的核心方法
-
wait()
系列方法- 作用:使当前线程释放锁并进入等待状态,直到其他线程调用
notify()
/notifyAll()
或超时。 - 重载方法:
wait()
:无限等待。wait(long timeout)
:等待指定毫秒。wait(long timeout, int nanos)
:更高精度超时控制。
- 使用条件:必须在同步代码块(
synchronized
)内调用,否则抛出IllegalMonitorStateException
。
- 作用:使当前线程释放锁并进入等待状态,直到其他线程调用
-
notify()
和notifyAll()
notify()
:随机唤醒一个等待该对象锁的线程,被唤醒的线程需重新竞争锁。notifyAll()
:唤醒所有等待该对象锁的线程。- 注意事项:同样必须在同步代码块内调用。