[Java并发] [1] synchronized 锁升级 偏向锁 轻量级锁 自旋锁笔记

来自阅读Java并发编程的艺术读书笔记,按照自己思路写了一些整理,综合了网上的一些博文和资料
本篇来源于Java并发编程的艺术 Ch2.2 内容


参考资料:
Java 并发编程的艺术
浅谈偏向锁、轻量级锁、重量级锁
Java并发——Synchronized关键字和锁升级,详细分析偏向锁和轻量级锁的升级

synchronized 实现原理与应用

首先理清synchronized与重量级锁、(偏向锁 轻量级锁 自旋锁)的关系

  • synchronized 是一个很好用的Java关键字,实现上锁互斥的功能;最初的synchronized仅支持重量级锁(Monitor)性能可能不让人满意
  • 后序,随着Java升级,添加了自旋锁、偏向锁、轻量级锁等锁提升性能,但是我们无需单独使用这些锁,只需要加上synchronized关键字,便可享受Java升级带来的性能提升,其他的事情Java都帮我们做好了
    • 其中,自旋锁并不是一种单独的锁机制,它存在于轻量级锁中,用于自旋等待以避免上下文线程切换
    • 偏向锁并非真正加锁,也不能主动解除,只能在存在抢占时膨胀到轻量级锁/在不存在抢占时由另一个线程降为无锁
    • 轻量级锁是一种利用自旋等待的锁机制,不适用于单CPU / 计算集中型业务;追求响应时间

最初,可以将synchronized理解成重量级锁,但是JSE1.6+对其进行优化引入更清量的锁后,本质上就没那么重了

  • synchronized实现同步的基础:Java中的每一个对象都可以作为锁
    • 普通同步方法:锁是当前实例对象
    • 静态同步方法:锁是当前类Class对象
    • 同步代码块:synchronzied (obj) {}括号内配置的对象
  • JVM中,对于synchronized同步代码块和同步方法基于Monitor对象实现
    • monitorenter指令插入到同步代码块的开始位置
    • monitorexit指令插入到方法结束处和异常处
    • JVM保证enter/exit成对出现,一般代码块同步使用上述指令,但是方法的同步同样可以使用上述指令完成

Java 对象头 - synchronized 锁

Java对象头内容结构:

image-20210125122214430

其中,Mark Word与锁有关。32位JVM中Mark Word状态如下

image-20210125122254232
image-20210125122306462

  • 要注意的是

    • 轻量级锁指向栈中锁记录指针
    • 重量级锁:指向 互斥量(重量级锁) 指针
  • 最初synchronized内置锁可以直接认为对应底层操作的互斥量mutex(保证一个时刻只有一个线程访问),成本很高,需要系统调用引起内核态/用户态切换、线程阻塞导致线程切换等,被称为重量级锁

锁的升级:自旋锁、自适应自旋锁、轻量级锁与偏向锁

内置锁是JVM提供的最便捷的线程同步工具:只要在代码块或方法声明上添加synchronized关键字,即可使用内置锁。

随着JVM的升级出现偏向锁、轻量级锁等,几乎不需要修改代码就可以享受优化结果

下面介绍几种升级后的锁,需要结合上面Mark Word的结构图看。首先上总结

由轻量到重量:

  • 偏向锁(无锁,不能主动释放,有竞争就膨胀)
    -> 轻量级锁(自旋等待一会)
    -> 重量级锁

  • 自旋锁/自适应自旋锁是**轻量级CAS失败后尝试动作,用于减少线程切换的开销

详细的加锁流程图,来源DreamToBe

image-20210125153715057

自旋锁

为了减少用户态/内核态切换(以切换运行线程)带来的开销,如果其他线程持有锁时间较短,则竞争锁的线程可以自旋(空转),以避免线程阻塞带来的上下文切换

  • 具体如下:

    • 当前线程竞争锁失败时,打算阻塞自己
      此时不直接阻塞自己,而是自旋(空转一会),空转时尝试竞争锁
    • 若自旋时获得锁,则锁获得成功;否则,自旋结束后阻塞自己
  • 适用范围:多核处理器

    • 持有时间比较短(自旋一会,占有锁进程就放开了)
    • 竞争时间比较短(A线程快要释放时,B线程才来竞争)
      即锁持有时间长,但竞争不激烈
  • 缺点:

    • 单核处理器中,不存在实际上的并行;线程多处理器少也浪费
      若线程B不阻塞自己,则线程A(owner)永远不能执行,锁永远不能释放,自旋多久都是浪费;若线程多而处理器少,自旋也会造成浪费

    • 自旋 占用CPU,计算密集场景下得不偿失

    • 如果锁竞争时间长,则自旋通常不会获得锁,白浪费CPU时间
      若锁持有时间长,且竞争激烈,应主动禁用自旋锁

      -XX:-UseSpinning 参数关闭自旋锁优化
      -XX:PreBlockSpin 参数修改默认的自旋次数
      

自适应自旋锁

相对于自旋锁来说,自旋时间不固定,根据前一次在同一个锁上的自旋时间锁拥有者的状态决定

  • 策略:
    • 若在同一个锁对象中,自旋刚刚成功获得过锁,并且持有锁的线程正在运行
      JVM认为这次自旋也很有可能成功,允许更长时间的等待
    • 若对于某个锁,自旋很少成功过
      则以后获取这个锁时就要减少自旋、甚至省略自旋
  • 问题:如果默认自旋次数不合理(过高或过低),则很难将自旋时间收敛到合适的值

轻量级锁

自旋锁的目标是降低线程切换成本(或者说降低线程切换次数)

如果锁竞争激烈,则我们不得不依赖重量级锁,让竞争失败的线程阻塞;如果没有实际竞争,则申请重量级锁是浪费的。

轻量级锁的目的在于:减少 无实际竞争的情况下,使用重量级锁的性能损耗(包括系统调用-内核态/用户态切换、线程阻塞造成线程切换等)

image-20210125150843245
  • 原理:不需要申请互斥量

    • 仅仅将Mark Word中部分字节CAS更新到指向线程栈中的Lock Record

    • 若更新成功,则轻量级锁获取成功,将状态设置为轻量级锁

    • 若更新失败,则已有线程获得轻量级锁,目前发生锁竞争,接下来膨胀成重量级锁

      • 膨胀过程:将MarkWord修改为(重量级锁指针, 10),然后申请锁的线程阻塞

        当持有轻量级锁线程在CAS更新Mark Word以解锁时失败,即释放锁并等待竞争

  • 使用条件:锁竞争不是那么激烈的时候

    • 如果锁竞争激烈,将很快膨胀成重量级锁
    • 维持轻量级锁的过程就造成浪费
  • 如果存在锁竞争,但是不太激烈,依然可以用自旋锁优化

偏向锁

如果实际场景根本就没有竞争,使用锁的线程只有一个,那么使用轻量级锁都根本是浪费

image-20210125152642220
  • 基本原理
    • 当线程1访问代码块并尝试获取锁对象时,先比较当前线程threadId和Java对象头中threadId(偏向锁Mark Word)是否一致;
      • 如果一致,则说明还是线程1在获取(即重入),无须加锁解锁
    • 如果不一致,其他线程占有偏向锁,因为偏向锁不能主动释放,则查看owner是否存活
      • 如果不存活,则直接重置到***无锁状态***,其他线程可以竞争将其设置为偏向锁
      • 如果存活,则等待占有锁的线程进入安全区后暂停
        若其还继续占有,则对其撤销偏向锁,升级为轻量级锁
        若其不再继续占有,则设置为无锁,重新偏向新的线程

偏向锁的目标:减少无竞争且只有一个线程使用锁的情况下,使用轻量级锁的性能损耗

  • 偏向锁 vs 轻量级锁

    • 轻量级锁每次申请/释放锁都至少需要一次CAS
    • 偏向锁只有初始化才需要CAS
  • 缺点:如果明显存在其他线程竞争锁,则很快膨胀成轻量级锁(不过副作用少很多)

    使用参数-XX:-UseBiasedLocking=false 禁止偏向锁优化(默认打开)
    
    Java6/7中默认启用,但是在应用程序启动后几秒钟才会激活
    可以使用参数关闭延迟:-XX:BiasedLockingStartupDelay=0
    

锁对比

在这里插入图片描述

锁不可降级

  • Java中的锁一旦升级就不可降级,但是偏向锁可以降级为无锁

锁粗化

  • 按理讲,同步代码块越小越好;但是如果临近两个临界区被分开,频繁的加锁解锁竞争锁也会造成不必要的性能损失
  • 所以临近的同步代码块,可以粗化为同一个

锁消除

  • Java虚拟机在JIT编译时(可以简单理解为当某段代码即将第一次被执行时进行编译,又称即时编译),通过对运行上下文的扫描,经过逃逸分析,去除不可能存在共享资源竞争的锁,通过这种方式消除没有必要的锁,可以节省毫无意义的请求锁时间
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值