偏向锁,轻量级锁,重量级锁的加锁过程
https://blog.csdn.net/lengxiao1993/article/details/81568130
Java SE1.6 为了改善性能, 使得 JVM 会根据竞争情况, 使用如下 3 种不同的锁机制
- 偏向锁(Biased Lock )
- 轻量级锁( Lightweight Lock)
- 重量级锁(Heavyweight Lock)
上述这三种机制的切换是根据竞争激烈程度进行的, 在几乎无竞争的条件下, 会使用偏向锁, 在轻度竞争的条件下, 会由偏向锁升级为轻量级锁, 在重度竞争的情况下, 会升级到重量级锁。
1.偏向锁
注意 JVM 提供了关闭偏向锁的机制, JVM 启动命令指定如下参数即可-XX:-UseBiasedLocking
根据偏斜锁机制是否打开, 对象 MarkWord 状态以不同方式转换的过程
无锁 -> 偏向锁
偏向锁的获取方式是将对象头的 MarkWord 部分中, 标记上线程ID, 以表示哪一个线程获得了偏向锁。
1.如果为可偏向状态, 则尝试用 CAS 操作, 将自己的线程 ID 写入MarkWord
- 如果 CAS 操作成功(状态转变为下图), 则认为已经获取到该对象的偏向锁, 执行同步块代码 。
- 补充: 一个线程在执行完同步代码块以后, 并不会尝试将 MarkWord 中的 thread ID 赋回原值 。这样做的好处是: 如果该线程需要再次对这个对象加锁,而这个对象之前一直没有被其他线程尝试获取过锁,依旧停留在可偏向的状态下, 即可在不修改对象头的情况下, 直接认为偏向成功。
如果 CAS 操作失败, 则说明, 有另外一个线程 Thread B 抢先获取了偏向锁。 这种状态说明该对象的竞争比较激烈, 此时需要撤销 Thread B 获得的偏向锁,将 Thread B 持有的锁升级为轻量级锁。 该操作需要等待全局安全点 JVM safepoint ( 此时间点, 没有线程在执行字节码)。
2.如果是已偏向状态, 则检测 MarkWord 中存储的 thread ID 是否等于当前 thread ID 。
- 如果相等, 则证明本线程已经获取到偏向锁, 可以直接继续执行同步代码块
如果不等, 则证明该对象目前偏向于其他线程, 需要撤销偏向锁
偏向锁的撤销(Revoke)
如上文提到的, 偏向锁的撤销(Revoke) 操作并不是将对象恢复到无锁可偏向的状态, 而是在偏向锁的获取过程中, 发现了竞争时, 直接将一个被偏向的对象“升级到” 被加了轻量级锁的状态。 这个操作的具体完成方式如下:
- 在偏向锁 CAS 更新操作失败以后, 等待到达全局安全点。
通过 MarkWord 中已经存在的 Thread Id 找到成功获取了偏向锁的那个线程, 然后在该线程的栈帧中补充上轻量级加锁时, 会保存的锁记录(Lock Record), 然后将被获取了偏向锁对象的 MarkWord 更新为指向这条锁记录的指针。
偏向锁 -> 轻量级锁
从之前的描述中可以看到, 存在超过一个线程竞争某一个对象时, 会发生偏向锁的撤销操作。 有趣的是, 偏向锁撤销后, 对象可能处于两种状态。
一种是不可偏向的无锁状态, 如下图(之所以不允许偏向, 是因为已经检测到了多于一个线程的竞争, 升级到了轻量级锁的机制)
另一种是不可偏向的已锁 ( 轻量级锁) 状态
之所以会出现上述两种状态, 是因为偏向锁不存在解锁的操作, 只有撤销操作。 触发撤销操作时:
- 原来已经获取了偏向锁的线程可能已经执行完了同步代码块, 使得对象处于 “闲置状态”,相当于原有的偏向锁已经过期无效了。此时该对象就应该被直接转换为不可偏向的无锁状态。
原来已经获取了偏向锁的线程也可能尚未执行完同步代码块, 偏向锁依旧有效, 此时对象就应该被转换为被轻量级加锁的状态
轻量级加锁过程:
- 首先根据标志位判断出对象状态处于不可偏向的无锁状态( 如下图)
- 在当前线程的栈桢(Stack Frame)中创建用于存储锁记录(lock record)的空间,并将对象头中的Mark Word复制到锁记录中,官方称为Displaced Mark Word。如果在此过程中发现,
- 然后线程尝试使用 CAS 操作将对象头中的 Mark Word 替换为指向锁记录的指针。
-
- 如果成功,当前线程获得锁
- 如果失败,表示该对象已经被加锁了, 先进行自旋操作, 再次尝试 CAS 争抢, 如果仍未争抢到, 则进一步升级锁至重量级锁。