Administrator
Published on 2021-12-15 / 152 Visits
0
0

JUC之Synchronized与锁升级

Synchronized与锁升级

Synchronized的性能变化

  • java5以前,只有Synchronized,这个是操作系统级别的重量级操作
  • 因为监视器锁(monitor)是依赖于底层的操作系统的 Mutex Lock 来实现的,挂起线程和恢复线程都需要转入内核态去完成,阻塞或唤醒一个Java线程需要操作系统切换CPU状态来完成,这种状态切换需要耗费处理器时间,如果同步代码块中内容过于简单,这种切换的时间可能比用户代码执行的时间还长”,时间成本相对较高,这也是为什么早期的synchronized效率低的原因
  • Java 6之后,为了减少获得锁和释放锁所带来的性能消耗,引入了轻量级锁和偏向锁

synchronized 锁升级步骤

无锁

  • 程序不会有锁的竞争

偏向锁

作用

  • 当一段同步代码一直被同一个线程多次访问,由于只有一个线程那么该线程在后续访问时便会自动获得锁

实现原理

  • 一个synchronized方法被一个线程抢到了锁时,那这个方法所在的对象就会在其所在的 Mark Word 中将偏向锁修改状态位,同时还会有占用前54位来存储线程指针作为标识
  • 若该线程再次访问同一个synchronized方法时,该线程只需去对象头的 Mark Word 中去判断一下是否有偏向锁指向本身的ID,无需再进入 Monitor 去竞争对象了

JVM 命令

  • 实际上偏向锁在JDK1.6之后是默认开启的,但是启动时间有延迟,

  • 所以需要添加参数 -XX:BiasedLockingStartupDelay=0,让其在程序启动时立刻启动

  • 开启偏向锁

    • -XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0
  • 关闭偏向锁

    • 关闭之后程序默认会直接进入轻量级锁状态

    • -XX:-UseBiasedLocking

偏向锁撤销

  • 偏向锁使用一种等到竞争出现才释放锁的机制,只有当其他线程竞争锁时,持有偏向锁的原来线程才会被撤销
  • 撤销需要等待全局安全点(该时间点上没有字节码正在执行),同时检查持有偏向锁的线程是否还在执行:
    • 第一个线程正在执行synchronized方法(处于同步块),它还没有执行完,其它线程来抢夺,该偏向锁会被取消掉并出现锁升级
      此时轻量级锁由原持有偏向锁的线程持有,继续执行其同步代码,而正在竞争的线程会进入自旋等待获得该轻量级锁
    • 第一个线程执行完成synchronized方法(退出同步块),则将对象头设置成无锁状态并撤销偏向锁,重新偏向

轻量级锁(自旋锁)

  • 轻量级锁是为了在线程近乎交替执行同步块时提高性能
  • 主要目的: 在没有多线程竞争的前提下,通过CAS减少重量级锁使用操作系统互斥量产生的性能消耗,说白了先自旋再阻塞
  • 升级时机: 当关闭偏向锁功能或多线程竞争偏向锁会导致偏向锁升级为轻量级锁
  • 假如线程A已经拿到锁,这时线程B又来抢该对象的锁,由于该对象的锁已经被线程A拿到,当前该锁已是偏向锁了,而线程B在争抢时发现对象头 Mark Word 中的线程ID不是线程B自己的线程ID(而是线程A),那线程B就会进行CAS操作希望能获得锁
  • 此时线程B操作中有两种情况:
    • 如果锁获取成功,直接替换Mark Word中的线程ID为B自己的ID(A → B),重新偏向于B线程,该锁会保持偏向锁状态,A线程Over,B线程上位
    • 如果锁获取失败,则偏向锁升级为轻量级锁,此时轻量级锁由原持有偏向锁的线程持有,继续执行其同步代码,而正在竞争的线程B会进入自旋等待获得该轻量级锁

轻量锁与偏向锁的区别和不同

  • 争夺轻量级锁失败时,自旋尝试抢占锁
  • 轻量级锁每次退出同步块都需要释放锁,而偏向锁是在竞争发生时才释放锁

重量级锁

有大量的线程参与锁的竞争,冲突性很高

总结锁的优缺点

  • synchronized锁升级过程总结:一句话,就是先自旋,不行再阻塞

  • 实际上是把之前的悲观锁(重量级锁)变成在一定条件下使用偏向锁以及使用轻量级(自旋锁CAS)的形式

  • synchronized在修饰方法和代码块在字节码上实现方式有很大差异,但是内部实现还是基于对象头的MarkWord来实现的,JDK1.6之前synchronized使用的是重量级锁,JDK1.6之后进行了优化,拥有了无锁->偏向锁->轻量级锁->重量级锁的升级过程,而不是无论什么情况都使用重量级锁

  • 偏向锁适用于单线程适用的情况,在不存在锁竞争的时候进入同步方法/代码块则使用偏向锁

  • 轻量级锁适用于竞争较不激烈的情况(这和乐观锁的使用范围类似), 存在竞争时升级为轻量级锁,轻量级锁采用的是自旋锁,如果同步方法/代码块执行时间很短的话,采用轻量级锁虽然会占用cpu资源但是相对比使用重量级锁还是更高效

  • 重量级锁适用于竞争激烈的情况,如果同步方法/代码块执行时间很长,那么使用轻量级锁自旋带来的性能消耗就比使用重量级锁更重,这时候就需要升级为重量级锁

JIT 对锁的优化

锁消除

锁粗化


Comment