https://blog.csdn.net/RollingCode_999/article/details/145591346
上一篇讲了为什么需要同步,以及 synchronized 关键字的使用,这一篇分析一下 synchronized的底层原理
对象头中的Mark Word
每个对象头中都包含了对象头信息,与锁相关的关键部分是Mark Word(标记字段)
Mark Word 会根据锁状态动态变化,存储锁标志位,线程ID等信息
以下为 Mark Word 的四种锁状态信息:
- Normal(无锁)
- Biased(偏向锁)
- Lightweight Locked(轻量级锁)
- ptr_to_heavyweight_monitor:62 (重量级锁)
|---------------------------------------------------|--------------------|
| Mark Word (64 bits) | State |
|---------------------------------------------------|--------------------|
| unused:25 | identity_hashcode:31 | unused:1 | age:4 | biased_lock:0 | 01 | Normal(无锁)
|---------------------------------------------------|--------------------|
| thread:54 | epoch:2 | unused:1 | age:4 | biased_lock:1 | 01 | Biased(偏向锁)
|---------------------------------------------------|--------------------|
| ptr_to_lock_record:62 | 00 | Lightweight Locked(轻量级锁)
|---------------------------------------------------|--------------------|
| ptr_to_heavyweight_monitor:62 | 10 | Heavyweight Locked(重量级锁)
|---------------------------------------------------|--------------------|
Mark Word 信息变化过程
无锁状态
没有线程访问同步代码块,biased_lock:0
偏向锁(Biased Locking)
当第一个线程访问同步代码块时,Mark Word 记录该线程的ID,biased_lock:1
后续如果是同一个线程进入该代码块时,直接检查线程ID是否和记录的一样,一样则不需要加锁
轻量级锁(Lightweight Locking)
当有第二个线程尝试获取锁的时候,偏向锁会升级成为轻量级锁
线程在栈帧中创建 Lock Record,通过CAS算法将 Mark Word 复制到 Lock Record,并尝试用CAS将 Mark Word 替换为指向 Lock Record 的指针(ptr_to_lock_record)
若CAS成功,获得锁。此时 State: 00
重量级锁(Heavyweight Locking)
若多个线程在获取轻量级锁时,CAS操作失败,并且重试一定次数后依然没有获得锁,说明锁竞争激烈。轻量级锁升级为重量级锁
此时,JVM会在堆中创建一个 ObjectMonitor 实例(Monitor),将 Mark Word 中的内容替换为指向 Monitor 的指针(ptr_to_heavyweight_monitor)
当一个线程获得锁时,其他竞争的线程会被放入 Monitor 的 EntryList,等待操作系统级互斥锁(Mutex)的调度
- 线程竞争轻量级锁时,创建的Lock Record存放在线程栈帧中,对象中的 Mark Word指向的是栈帧中的Lock Record
- 尝试使用CAS操作竞争轻量级锁失败的线程会自旋尝试获得锁
- 多个线程CAS 失败超过一定次数,会升级为重量级锁,此时才会创建ObjectMonitor
- ObjectMonitor 并不是一开始就创建好了,是在锁升级到重量级锁的时候才创建的
绑定Monitor的流程
当锁需要升级为重量级锁时,会创建ObjectMonitor对象绑定到Mark Word,具体步骤如下:
- JVM创建ObjectMonitor对象(即Monitor),结构包含Owner、EntryList、WaitSet等字段。
- 竞争的线程尝试通过CAS操作,将Mark Word的标志位(State)从00更新到10
- 如果更新成功,将Mark Word替换为指向Monitor的指针,将当前线程设置为Monitor的Owner
- 如果更新失败,表示已经有其他线程持有锁,线程进入阻塞(竞争的线程被放入EntryList,等待操作系统调度)
- 线程执行完释放锁时,将Monitor中的Owner设置为null,唤醒 EntryList 或者 WaitSet 中的线程
Monitor结构
+-------------------+
| Owner | -> 持有锁的线程
+-------------------+
| EntryList | -> 竞争锁的线程队列(BLOCKED 状态)
+-------------------+
| WaitSet | -> 调用 wait() 的线程队列(WAITING 状态)
+-------------------+