为了换取性能,JVM在内置锁上做了非常多的优化,膨胀式的锁分配策略就是其一。理解偏向锁、轻量级锁、重量级锁的要解决的基本问题,几种锁的分配和膨胀过程,有助于编写并优化基于锁的并发程序。

内置锁的分配和膨胀过程较为复杂,限于时间和精力,文中该部分内容是根据网上的多方资料整合而来;仅为方便查阅,后面继续分析JVM源码的时候也有个参考。如果对各级锁已经有了基本了解,读者大可跳过此文。

1 隐藏在内置锁下的基本问题

内置锁是JVM提供的最便捷的线程同步工具,在代码块或方法声明上添加synchronized关键字即可使用内置锁。使用内置锁能够简化并发模型;随着JVM的升级,几乎不需要修改代码,就可以直接享受JVM在内置锁上的优化成果。从简单的重量级锁,到逐渐膨胀的锁分配策略,使用了多种优化手段解决隐藏在内置锁下的基本问题。

1.1 重量级锁

内置锁在Java中被抽象为监视器锁(monitor)。在JDK 1.6之前,监视器锁可以认为直接对应底层操作系统中的互斥量(mutex)。这种同步方式的成本非常高,包括系统调用引起的内核态与用户态切换、线程阻塞造成的线程切换等。因此,后来称这种锁为“重量级锁”。

1.1.1 自旋锁

首先,内核态与用户态的切换上不容易优化。但通过自旋锁,可以减少线程阻塞造成的线程切换(包括挂起线程和恢复线程)。

如果锁的粒度小,那么锁的持有时间比较短(尽管具体的持有时间无法得知,但可以认为,通常有一部分锁能满足上述性质)。那么,对于竞争这些锁的而言,因为锁阻塞造成线程切换的时间与锁持有的时间相当,减少线程阻塞造成的线程切换,能得到较大的性能提升。具体如下:

  • 当前线程竞争锁失败时,打算阻塞自己

  • 不直接阻塞自己,而是自旋(空等待,比如一个空的有限for循环)一会

  • 在自旋的同时重新竞争锁

  • 如果自旋结束前获得了锁,那么锁获取成功;否则,自旋结束后阻塞自己

如果在自旋的时间内,锁就被旧owner释放了,那么当前线程就不需要阻塞自己(也不需要在未来锁释放时恢复),减少了一次线程切换。

“锁的持有时间比较短”这一条件可以放宽。实际上,只要锁竞争的时间比较短(比如线程1快释放锁的时候,线程2才会来竞争锁),就能够提高自旋获得锁的概率。这通常发生在锁持有时间长,但竞争不激烈的场景中。

缺点:

  • 单核处理器上,不存在实际的并行,当前线程不阻塞自己的话,旧owner就不能执行,锁永远不会释放,此时不管自旋多久都是浪费;进而,如果线程多而处理器少,自旋也会造成不少无谓的浪费。

  • 自旋锁要占用CPU,如果是计算密集型任务,这一优化通常得不偿失,减少锁的使用是更好的选择。

  • 如果锁竞争的时间比较长,那么自旋通常不能获得锁,白白浪费了自旋占用的CPU时间。这通常发生在锁持有时间长,且竞争激烈的场景中,此时应主动禁用自旋锁。

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

1.1.2 自适应自旋

自适应意味着自旋的时间不再固定了,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定:

  • 如果在同一个锁对象上,自旋等待刚刚成功获得过锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋也很有可能再次成功,进而它将允许自旋等待持续相对更长的时间,比如100个循环。

  • 相反的,如果对于某个锁,自旋很少成功获得过,那在以后要获取这个锁时将可能减少自旋时间甚至省略自旋过程,以避免浪费处理器资源。

自适应自旋解决的是“锁竞争时间不确定”的问题。

JVM很难感知到确切的锁竞争时间,而交给用户分析就违反了JVM的设计初衷。自适应自旋假定不同线程持有同一个锁对象的时间基本相当,竞争程度趋于稳定,因此,可以根据上一次自旋的时间与结果调整下一次自旋的时间。

缺点:

然而,自适应自旋也没能彻底解决该问题,如果默认的自旋次数设置不合理(过高或过低),那么自适应的过程将很难收敛到合适的值。

1.2 轻量级锁

自旋锁的目标是降低线程切换的成本。如果锁竞争激烈,我们不得不依赖于重量级锁,让竞争失败的线程阻塞;如果完全没有实际的锁竞争,那么申请重量级锁都是浪费的。轻量级锁的目标是,减少无实际竞争情况下,使用重量级锁产生的性能消耗,包括系统调用引起的内核态与用户态切换、线程阻塞造成的线程切换等。

顾名思义,轻量级锁是相对于重量级锁而言的。使用轻量级锁时,不需要申请互斥量,仅仅_将Mark Word中的部分字节CAS更新指向线程栈中的Lock Record,如果更新成功,则轻量级锁获取成功_,记录锁状态为轻量级锁;否则,说明已经有线程获得了轻量级锁,目前发生了锁竞争(不适合继续使用轻量级锁),接下来膨胀为重量级锁。

Mark Word是对象头的一部分;每个线程都拥有自己的线程栈(虚拟机栈),记录线程和函数调用的基本信息。二者属于JVM的基础内容,此处不做介绍。

当然,由于轻量级锁天然瞄准不存在锁竞争的场景,如果存在锁竞争但不激烈,仍然可以用自旋锁优化,自旋失败后再膨胀为重量级锁。

缺点:

同自旋锁相似:

  • 如果锁竞争激烈,那么轻量级将很快膨胀为重量级锁,那么维持轻量级锁的过程就成了浪费。

1.3 偏向锁

在没有实际竞争的情况下,还能够针对部分场景继续优化。如果不仅仅没有实际竞争,自始至终,使用锁的线程都只有一个,那么,维护轻量级锁都是浪费的。偏向锁的目标是,减少无竞争且只有一个线程使用锁的情况下,使用轻量级锁产生的性能消耗。轻量级锁每次申请、释放锁都至少需要一次CAS,但偏向锁只有初始化时需要一次CAS。

“偏向”的意思是,偏向锁假定将来只有第一个申请锁的线程会使用锁(不会有任何线程再来申请锁),因此,只需要在Mark Word中CAS记录owner(本质上也是更新,但初始值为空),如果记录成功,则偏向锁获取成功,记录锁状态为偏向锁,以后当前线程等于owner就可以零成本的直接获得锁;否则,说明有其他线程竞争,膨胀为轻量级锁。

偏向锁无法使用自旋锁优化,因为一旦有其他线程申请锁,就破坏了偏向锁的假定。

缺点:

同样的,如果明显存在其他线程申请锁,那么偏向锁将很快膨胀为轻量级锁。

不过这个副作用已经小的多。
如果需要,使用参数-XX:-UseBiasedLocking禁止偏向锁优化(默认打开)。

1.4 小结

偏向锁、轻量级锁、重量级锁分配和膨胀的详细过程见后。会涉及一些Mark Word与CAS的知识。

偏向锁、轻量级锁、重量级锁适用于不同的并发场景:

  • 偏向锁:无实际竞争,且将来只有第一个申请锁的线程会使用锁。

  • 轻量级锁:无实际竞争,多个线程交替使用锁;允许短时间的锁竞争。

  • 重量级锁:有实际竞争,且锁竞争时间长。

另外,如果锁竞争时间短,可以使用自旋锁进一步优化轻量级锁、重量级锁的性能,减少线程切换。

如果锁竞争程度逐渐提高(缓慢),那么从偏向锁逐步膨胀到重量锁,能够提高系统的整体性能。

2 锁分配和膨胀过程

重申,这部分主要是根据网上的多方资料整理。核心是这位 巨巨整理的流程图,相当详细,基本符合逻辑。

前面讲述了内置锁在使用过程中的一些基本问题和解决方案,实现原理一笔带过。详细的锁分配和膨胀过程如下:

图中有一处疑问:
按照图中流程, 如果发现锁已经膨胀为重量级锁,就直接使用互斥量mutex阻塞当前线程。
然而,自旋锁的一大好处就是减少线程切换的开销。在这里没有必要直接阻塞当前线程,大可以像轻量级锁一样,自旋一会,失败了再阻塞。

特别说明两点:

  • CAS记录owner时,expected == null,newValue == ownerThreadId,因此,只有第一个申请偏向锁的线程能够返回成功,后续线程都必然失败(部分线程检测到可偏向,同时尝试CAS记录owner)。

  • 内置锁只能沿着偏向锁、轻量级锁、重量级锁的顺序逐渐膨胀,不能“收缩”。这基于JVM的另一个假定,“一旦破坏了上一级锁的假定,就认为该假定以后也必不成立”。

另外,当重量级锁被解除后,需要唤醒一个被阻塞的线程,这部分逻辑与ReentrantLock基本相同

简化上图显示:

偏向锁、轻量级锁、重量级锁的区别和解析相关推荐

  1. synchronized锁升级之重量级锁

    目录 一.什么是重量级锁? 二.重量级锁的演示 三.重量级锁的原理 四.锁的优缺点对比 一.什么是重量级锁? 当有大量的线程都在竞争同一把锁的时候,这个时候加的锁,就是重量级锁. 这个重量级锁其实指的 ...

  2. 知识扩展——轻量级和重量级框架的区别

    不管是iOS开发还是前端.Java.Android开发中,我们经常需要用到第三方库,而在搜索第三方库的介绍和使用文档时,经常会看到轻量级.重量级等字眼,那么轻量级框架和重量级框架是怎么区分的呢? 判定 ...

  3. Synchronized的原理及自旋锁,偏向锁,轻量级锁,重量级锁的区别

    在多线程并发编程中Synchronized一直是元老级角色,很多人都会称呼它为重量级锁,但是随着Java SE1.6对Synchronized进行了各种优化之后,有些情况下它并不那么重了,Java S ...

  4. 锁升级过程(无锁、偏向锁、轻量级锁、重量级锁)

    文章目录 Synchronized锁升级的背景 Synchronized的性能变化 Java5之前,用户态和内核态之间的切换 为什么每个对象都可以称为一把锁? Java6开始优化Synchronize ...

  5. 偏向锁、轻量级锁、重量级锁加锁过程即锁升级膨胀过程

    偏向锁.轻量级锁.重量级锁加锁过程即锁升级膨胀过程 synchronized 偏向锁 为什么要引入偏向锁 偏向锁加锁过程 线程获取到锁对象的偏向锁之后,执行完同步代码块之后,会释放这个偏向锁吗 使用了 ...

  6. java中锁的基本原理和升级:偏向锁、轻量级锁、重量级锁

    目录 由一个问题引发的思考 多线程对于共享变量访问带来的安全性问题 线程安全性 思考如何保证线程并行的数据安全性 synchronized 的基本认识 synchronized 的基本语法 synch ...

  7. java——无锁、偏向锁、轻量级锁、重量级锁的synchronize锁升级笔记

    本章所需基础知识: 懂得多线程和锁的基础知识就行 或者看完我上一篇的<java多进程和多线程简单复习(不涉及原理)>就可以了 如果没基础建议别看 推荐视频: B站马士兵老师的视频:无锁.偏 ...

  8. Java中的锁机制 -- 乐观锁、悲观锁、自旋锁、可重入锁、读写锁、公平锁、非公平锁、共享锁、独占锁、重量级锁、轻量级锁、偏向锁、分段锁、互斥锁、同步锁、死锁、锁粗化、锁消除

    文章目录 1. Java中的锁机制 1.1 乐观锁 1.2 悲观锁 1.3 自旋锁 1.4 可重入锁(递归锁) 1.5 读写锁 1.6 公平锁 1.7 非公平锁 1.8 共享锁 1.9 独占锁 1.1 ...

  9. 简单理解重量级锁、轻量级锁、偏向锁

    全文使用synchronized来说明. synchronized给对象上锁,先上偏向锁,在上轻量级锁,最后上重量级锁.上什么锁,是gvm根据竞争程度自行变换的. 重量级锁 计算机操作系统本有Moni ...

最新文章

  1. 剑指Offer Ⅱ 005.单词长度的最大乘积 (力扣剑指Offer专项突击版——整数_5)
  2. 创建支持nginx服务的docker镜像
  3. binlog二进制文件解析
  4. SQLServer如何删除字段中的某个字符串,或者替换为空格?
  5. ubuntu ln软连接硬连接
  6. tbase同步mysql_mysql主从同步
  7. 【caffe】ubuntu配置matlab接口----matcaffe
  8. 微信小程序获取用户信息并存入数据库
  9. 安全清理大部分的C盘内存(一般10GB以上)
  10. Linux下使用锐捷客户端连接网络,以及遇到的问题
  11. c语言编程线性规划,C语言大作报告_线性规划求解_基科3字班
  12. Springboot毕设项目公共机房的值班管理系统wyz7b(java+VUE+Mybatis+Maven+Mysql)
  13. ctf 隐写术 老鹰抓小鸡
  14. 燕千云知识库,解决你的知识沉淀烦恼
  15. 内存的分类以及各自特征
  16. JavaScript # 前端 js、html中的单引号、双引号及其转义使用
  17. Python实现自由爆率抽奖小程序
  18. 【转载】联想ThinkPad X390笔记本装win7系统及BIOS设置方法
  19. 偏财入财库大富_什么是八字有财库者大富
  20. 哥德巴赫猜想的证明(李扩继)

热门文章

  1. odoo14Vscode调试环境
  2. 武汉市洪山区高新技术企业认定奖励标准及申请材料、条件、要求
  3. 支付必测--使用fiddler篡改支付金额
  4. 华为设备用户接入与认证配置命令
  5. proc文件系统下各参数解析
  6. 关于360旗下Atlas运维记事
  7. 用计算机发传真,如何用电脑发网络传真?在电脑里怎么发传真?
  8. 大数据主导的七大领域,看看你是否身处其中!
  9. 小程序获取节点绑定数据data-index的方法
  10. (附源码)spring boot网上商品定制系统 毕业设计180915