文章目录
- Java对象头
- 偏向锁
- 批量重偏向
- 批量撤销
- 轻量级锁
- 重量级锁
- Monitor 原理
Java对象头
普通对象
Mark Word在64 位虚拟机中的结构为
Mark Word后三(两)位表示
- 001:无锁状态
- 101: 偏向锁
- 00: 轻量级锁
- 10:重量级锁
偏向锁
Jdk6开始,默认开启偏向锁。当线程获取锁资源时,只有第一次使用 CAS 将线程 ID 设置到对象的 Mark Word
头,之后发现这个线程 ID 是自己的就表示没有竞争,不用重新 CAS。以后只要不发生竞争,这个对象就归该线程所有。
- 当锁释放之后,另一线程获得锁资源,则偏向锁升级为轻量级锁。
- 若锁还未释放,另一线程就来竞争锁资源,则偏向锁直接升级为重量级锁。
- 调用
wait/notify
方法时,偏向锁直接升级为重量级锁,因为只有重量级锁才有该方法。 - 调用
hashcode
方法,因为没有空间存储hashcode
,偏向锁自动撤销,变为无锁状态。
批量重偏向
如果一个类的大量对象被一个线程T1执行了同步操作,也就是大量对象先偏向了T1,T1同步结束后,另一个线程也将这些对象作为锁对象进行操作,会导偏向锁重偏向的操作。
BiasedLockingBulkRebiasThreshold
:偏向锁批量重偏向的默认阀值为20次。
批量撤销
当一个偏向锁如果撤销次数到达40的时候就认为这个对象设计的有问题;那么JVM会把这个对象所对应的类所有的对象都撤销偏向锁;并且新实例化的对象也是不可偏向的。
BiasedLockingBulkRevokeThreshold
:偏向锁批量撤销的默认阀值为40次。
批量重偏向和批量撤销是针对类的优化,和对象无关。偏向锁重偏向一次之后不可再次重偏向。当某个类已经触发批量撤销机制后,JVM会默认当前类产生了严重的问题,剥夺了该类的新实例对象使用偏向锁的权利。
轻量级锁
轻量级锁的使用场景:如果一个对象虽然有多线程要加锁,但加锁的时间是错开的(也就是没有竞争),那么可以使用轻量级锁来优化。
轻量级锁对使用者是透明的,即语法仍然是 synchronized
假设有两个方法同步块,利用同一个对象加锁。
轻量级锁的操作步骤如下:
- 创建锁记录(
Lock Record
)对象,每个线程都的栈帧都会包含一个锁记录的结构,内部可以存储锁定对象的Mark Word
- 让锁记录中
Object reference
指向锁对象,并尝试用 cas 替换 Object 的Mark Word
,将Mark Word
的值存入锁记录。
- 如果 cas 失败,有两种情况
- 如果是其它线程已经持有了该
Object
的轻量级锁,这时表明有竞争,进入锁膨胀过程。 - 如果是自己执行了
synchronized
锁重入,那么再添加一条Lock Record
作为重入的计数。
- 如果是其它线程已经持有了该
- 当退出
synchronized
代码块(解锁时)如果有取值为null
的锁记录,表示有重入,这时重置锁记录,表示重
入计数减一。 - 当退出
synchronized
代码块(解锁时)锁记录的值不为null
,这时使用 cas 将Mark Word
的值恢复给对象头。- 成功,则解锁成功。
- 失败,说明轻量级锁进行了锁膨胀或已经升级为重量级锁,进入重量级锁解锁流程。
重量级锁
Monitor 原理
每个 Java 对象都可以关联一个Monitor
对象,如果使用 synchronized
给对象上锁(重量级)之后,该对象头的
Mark Word
中就被设置指向Monitor
对象的指针。
Monitor 结构如下:
- 刚开始
Monitor
中Owner
为 null。 - 当
Thread-2
执行synchronized(obj)
就会将Monitor
的所有者Owner
置为Thread-2
,Monitor
中只能有一个Owner
。 - 在
Thread-2
上锁的过程中,如果Thread-3
,Thread-4
,Thread-5
也来执行synchronized(obj)
,就会进入
EntryList
BLOCKED。 Thread-2
执行完同步代码块的内容,然后唤醒EntryList
中等待的线程来竞争锁,竞争的时是非公平的。- 图中
WaitSet
中的Thread-0
,Thread-1
是之前获得过锁,但条件不满足进入调用wait()
方法进入 WAITING 状态的线程。当调用notifyAll()
方法之后,WaitSet
中的Thread-0
,Thread-1
进入EntryList
BLOCKED。