JVM学习笔记——内存模型篇( 六 )


  • 重量级锁竞争的时候 , 还可以使用自旋来进行优化
  • 如果当前线程自旋成功(即这时候持锁线程已经退出了同步块 , 释放了锁) , 这时当前线程就可以避免阻塞
我们对自旋进行简单补充:
  • 在 Java 6 之后自旋锁是自适应的
  • 比如对象刚刚的一次自旋操作成功过 , 那么认为这次自旋成功的可能性会高 , 就多自旋几次;反之 , 就少自旋甚至不自旋
  • 自旋会占用 CPU 时间 , 单核 CPU 自旋就是浪费 , 多核 CPU 自旋才能发挥优势 。
  • Java 7 之后不能控制是否开启自旋功能
首先我们给出自旋成功的流程展示:
线程 1 (cpu 1 上)对象 Mark线程 2 (cpu 2 上)-10(重量锁)-访问同步块 , 获取 monitor10(重量锁)重量锁指针-成功(加锁)10(重量锁)重量锁指针-执行同步块10(重量锁)重量锁指针-执行同步块10(重量锁)重量锁指针访问同步块 , 获取 monitor执行同步块10(重量锁)重量锁指针自旋重试执行完毕10(重量锁)重量锁指针自旋重试成功(解锁)01(无锁)自旋重试-10(重量锁)重量锁指针成功(加锁)-10(重量锁)重量锁指针执行同步块-......然后我们给出自旋失败的流程展示:
线程 1(cpu 1 上)对象 Mark线程 2(cpu 2 上)-10(重量锁)-访问同步块 , 获取 monitor10(重量锁)重量锁指针-成功(加锁)10(重量锁)重量锁指针-执行同步块10(重量锁)重量锁指针-执行同步块10(重量锁)重量锁指针访问同步块 , 获取 monitor执行同步块10(重量锁)重量锁指针自旋重试执行同步块10(重量锁)重量锁指针自旋重试执行同步块10(重量锁)重量锁指针自旋重试执行同步块10(重量锁)重量锁指针阻塞-......偏向锁我们首先来介绍一下偏向锁:
  • 轻量级锁在没有竞争时(就自己这个线程) , 每次重入仍然需要执行 CAS 操作 , Java 6 中引入了偏向锁来做进一步优化
  • 只有第一次使用 CAS 将线程 ID 设置到对象的 Mark Word 头 , 之后发现这个线程 ID是自己的就表示没有竞争 , 不用重新 CAS.
我们给出偏向锁的一些补充信息:
  • 撤销偏向需要将持锁线程升级为轻量级锁 , 这个过程中所有线程需要暂停(STW)
  • 访问对象的 hashCode 也会撤销偏向锁
  • 如果对象虽然被多个线程访问 , 但没有竞争 , 这时偏向了线程 T1 的对象仍有机会重新偏向 T2 , 重偏向会重置对象的 Thread ID
  • 撤销偏向和重偏向都是批量进行的 , 以类为单位
  • 如果撤销偏向到达某个阈值 , 整个类的所有对象都会变为不可偏向的
  • 可以主动使用 -XX:-UseBiasedLocking 禁用偏向锁
我们采用轻量级锁的代码但是加入了偏向锁之后的流程:
线程 1对象 Mark访问同步块 A , 检查 Mark 中是否有线程 ID101(无锁可偏向)尝试加偏向锁101(无锁可偏向)对象 hashCode成功101(无锁可偏向)线程ID执行同步块 A101(无锁可偏向)线程ID访问同步块 B , 检查 Mark 中是否有线程 ID101(无锁可偏向)线程ID是自己的线程 ID , 锁是自己的 , 无需做更多操作101(无锁可偏向)线程ID执行同步块 B101(无锁可偏向)线程ID执行完毕101(无锁可偏向)对象 hashCode其它优化我们下面来简单介绍一下其他的几种优化:
  1. 减少上锁时间
/*上锁期间的代码是影响上锁时间的最大因素我们应该确保同步代码块中尽量短*/

经验总结扩展阅读