JUC并发编程 -- 自旋优化 & 偏向锁

Posted Z && Y

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JUC并发编程 -- 自旋优化 & 偏向锁相关的知识,希望对你有一定的参考价值。

1. 自旋优化

重量级锁竞争的时候,还可以使用自旋来进行优化,如果当前线程自旋成功(即这时候持锁线程已经退出了同步块,释放了锁),这时当前线程就可以避免阻塞。

1.1 自旋重试成功的情况

自旋一定是多核CPU下才有意义


1.2 自旋重试失败的情况


1.3 补充:

  • 自旋会占用 CPU 时间,单核 CPU 自旋就是浪费,多核 CPU 自旋才能发挥优势。
  • 在 Java 6 之后自旋锁是自适应的,比如对象刚刚的一次自旋操作成功过,那么认为这次自旋成功的可能性会高,就多自旋几次;反之,就少自旋甚至不自旋,总之,比较智能。
  • Java 7 之后不能控制是否开启自旋功能(总是开启)

2. 偏向锁

  • 轻量级锁在没有竞争时(就自己这个线程),每次重入仍然需要执行 CAS 操作。
  • Java 6 中引入了偏向锁来做进一步优化:只有第一次使用 CAS 将线程 ID 设置到对象的 Mark Word 头,之后发现这个线程 ID 是自己的就表示没有竞争,不用重新 CAS。以后只要不发生竞争,这个对象就归该线程所有

2.1 示例

static final Object obj = new Object();
public static void m1() {
 synchronized( obj ) {
 // 同步块 A
 m2();
 }
}
public static void m2() {
 synchronized( obj ) {
 // 同步块 B
 m3();
 }
}
public static void m3() {
 synchronized( obj ) {
 // 同步块 C
 }
}

轻量级锁:

偏向锁:


2.2 偏向锁的状态


回忆一下对象头格式:

一个对象创建时:

  • 如果开启了偏向锁(默认开启),那么对象创建后,markword 值为 0x05 即最后 3 位为 101,这时它的thread、epoch、age 都为 0
  • 偏向锁是默认是延迟的,不会在程序启动时立即生效,如果想避免延迟,可以加 VM 参数 -XX:BiasedLockingStartupDelay=0 来禁用延迟
  • 如果没有开启偏向锁,那么对象创建后,markword 值为 0x01 即最后 3 位为 001,这时它的 hashcode、age 都为 0,第一次用到 hashcode 时才会赋值

2.3 撤销偏向锁


2.3.1 方法一: 调用对象 hashCode

调用了对象的 hashCode,但偏向锁的对象 MarkWord 中存储的是线程 id,如果调用 hashCode 会导致偏向锁被撤销(MarkWord中不可以同时存储这么多内容)。

  • 轻量级锁会在锁记录中记录 hashCode
  • 重量级锁会在 Monitor 中记录 hashCode

2.3.2 方法二: 其它线程使用对象

当有其它线程使用偏向锁对象时,会将偏向锁升级为轻量级锁。


2.3.3 方法三: 调用 wait/notify

只有重量级锁才有wait/notify这样的等待通知机制


2.4 批量重偏向

  • 如果对象虽然被多个线程访问,但没有竞争,这时偏向了线程 T1 的对象仍有机会重新偏向 T2,重偏向会重置对象的 Thread ID
  • 当撤销偏向锁阈值超过 20 次后,jvm 会这样觉得,我是不是偏向错了呢,于是会在给这些对象加锁时重新偏向至加锁线程。

2.5 批量撤销

当撤销偏向锁阈值超过 40 次后,jvm 会这样觉得,自己确实偏向错了,根本就不该偏向。于是整个类的所有对象都会变为不可偏向的,这个类新建的对象也是不可偏向的



以上是关于JUC并发编程 -- 自旋优化 & 偏向锁的主要内容,如果未能解决你的问题,请参考以下文章

Java线程并发中常见的锁--自旋锁 偏向锁

JVM中锁优化,偏向锁自旋锁锁消除锁膨胀

虚拟机中的锁优化简介(适应性自旋/锁粗化/锁削除/轻量级锁/偏向锁)

JUC并发编程 共享模式之工具 JUC 读写锁 StampedLock -- 介绍 & 使用

Java并发编程:Synchronized底层优化(偏向锁轻量级锁)

JUC并发编程 -- synchronized 原理进阶之轻量级锁 & 锁膨胀