一文带你了解Java并发中的锁优化,让你的代码运行效率翻倍,java基础入门pdf下载

Java多线程为了实现线程同步,加入同步锁(synchronized和lock机制)机制,同步锁的诞生虽然保证了操作的原子性、线程的安全性,但是(相比不加锁的情况下)造成了程序性能下降。所以,我们这里要做的一件事就是“锁优化”,即既要保证实现锁的功能(即保证多线程下操作安全)又要提高程序性能(即不要让程序因为安全而损失太大效率)。

下面来介绍HotSpot虚拟机(JVM)的锁优化措施,包括自旋与自适应自旋(Adaptive Spinning)、锁消除(Lock Elimination)、锁粗化(Lock Coarsening)、轻量级锁(Lightweight Locking)和偏向锁(Biased Locking)。这些技术都是为了在线程之间更高效地共享数据,以及解决竞争问题,从而提高程序的执行效率。

自旋锁与自适用自旋

=========

自旋锁定义:如果物理机器有一个以上的处理器,能让两个或以上的线程同时并行执行,我们就可以让后面请求锁的那个线程 “稍等一下”,但不放弃处理器的执行时间,看看持有锁的线程是否很快就会释放锁。为了让线程等待,我们只需让线程执行一个慢循环(自旋),这项技术就是所谓的自旋锁。

自旋锁具体业务流程:自旋等待不能代替阻塞,且先不说对处理器数量的要求,自旋等待本身虽然避免了线程切换的开销,但它是要占用处理器时间的,因此,如果锁被占用的时间很短,自旋等待的效果就会非常好,反之,如果锁被占用的时候很长,那么自旋的线程只会白白消耗处理器资源,而不会做任何有用的工作,反而会带来性能上的浪费。

因此,自旋等待的时间必须要有一定的限度,如果自旋超过了限定的次数仍然没有成功获得锁,就应当使用传统的方式去挂起线程了。自旋次数的默认值是 10 次,用户可以使用参数 -XX:PreBlockSpin 来更改。

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

自适用自旋具体业务流程:如果在同一个锁对象上,自旋等待刚刚成功获得过锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋也很有可能再次成功,进而它将允许自旋等待持续相对更长的时间,比如 100 个循环。另外,如果对于某个锁,自旋很少成功获得过,那在以后要获取这个锁时将可能省略掉自旋过程,以避免浪费处理器资源。

有了自适应自旋,随着程序运行和性能监控信息的不断完善,虚拟机对程序锁的状况预测就会越来越准确,虚拟机就会变得越来越 “聪明” 了

《一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》

【docs.qq.com/doc/DSmxTbFJ1cmN1R2dB】 完整内容开源分享

锁消除

===

锁消除定义:锁消除是指虚拟机即时编译器在运行时,对一些代码上要求同步,但是被检测到不可能存在共享数据竞争的锁进行消除(即经过JVM做一个锁消除优化,将确定没用的锁消除,是一种JVM并发优化技术)。

如何判断某个锁可以消除?

锁消除的主要判定依据来源于逃逸分析的数据支持,如果判定在一段代码中,堆上的所有数据都不会逃逸出去从而被其他线程访问到,那就可以把他们当做栈上数据对待,认为它们是线程私有的,同步加锁自然就无须进行(即对于只有该线程可以访问到其他线程无法访问的到的代码,可以大胆的做所消除)。

干货!一文带你了解Java并发中的锁优化,让你的代码运行效率翻倍

干货!一文带你了解Java并发中的锁优化,让你的代码运行效率翻倍

结合.java到.class字节码文件可以知道,在jdk8的情况下,return a+b+c;这句程序实际上底层被转换为StringBuilder的追加操作。

每个 StringBuffer.append() 方法中都有一个同步块,锁就是 sb 对象(StringBuilder新建的一个值为“”的空字符串对象)。虚拟机观察变量 sb,很快就会发现它的动态作用域被限制在 concatString() 方法内部。也就是说,sb 的所有引用永远不会 “逃逸” 道 concatString() 方法之外,其他线程无法访问到它,因此,虽然这里有锁,但是可以被安全地消除掉,在即时编译之后,这段代码就会忽略掉所有的同步而直接执行了(可以理解为StringBuilder虽然线程不安全,但是这里没有关系,因为整个执行过程都在main线程中,不会涉及任何线程同步,所以是安全的。这里底层使用StringBuilder就可以被认为是一种锁消除)。

锁粗化

===

锁粗化定义:这是一种JVM并发优化,如果虚拟机探测到由这样的一串零碎的操作都对同一个对象加锁,将会把加锁同步的范围扩展(粗化)到整个操作序列的外部,这就是锁粗化。

解释:以上面的return a+b+c;为例,底层被拆分为:

干货!一文带你了解Java并发中的锁优化,让你的代码运行效率翻倍

三个append()追加,如果对每一个append()都加锁操作,频繁地进行互斥同步操作也会导致不必要的性能损耗,这是JVM不乐意看到的,为了提高JVM并发性能,此时,JVM会进行一个优化,就是扩展到第一个 append() 操作之前直至最后一个 append() 操作之后,这样只需要加锁一次就可以了,这就是锁粗化。

轻量级锁

====

轻量级锁定义:一种JVM并发优化,是指在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗。

注意两个:

没有多线程竞争:是指这种轻量级锁只有在没有多线程竞争才能减少性能消耗(实际上,如果有多线程竞争,它的消耗比重量级锁更大,后面会讲)。

传统的重量级锁:是指synchronized关键字(和lock锁机制)。

引子:HotSpot 虚拟机的对象(对象头部分)的内存布局

HotSpot 虚拟机的对象头(Object Header)分为两部分信息,第一部分用于存储对象自身的运行时数据,如哈希码(HashCode)、GC 分代年龄(Generational GC Age)等,这部分数据是长度在 32 位和 64 位的虚拟机中分别为 32 bit 和 64 bit,官方称它为 “Mark Word”,它是实现轻量级锁和偏向锁的关键。

另外一部分用于存储指向方法区对象类型数据的指针,如果是数组对象的话,还会有一个额外的部分用于存储数组长度。

对象头信息是与对象自身定义的数据无关的额外存储成本,考虑到虚拟机的空间效率,Mark Work 被设计成一个非固定的数据结构以便在极小的空间内存储尽量多的信息,它会根据对象的状态复用自己的存储空间。HotSpot虚拟机对象头 Mark Word,如下表:

干货!一文带你了解Java并发中的锁优化,让你的代码运行效率翻倍

问题1:上表中为什么有两个01,是不是重复了?

上表的五个项中,第一个表示的是未锁定,其他四个均表示锁定,即第一个01表示的是未锁定,后面四个表示的都是锁定.

问题2:未锁定和偏向锁标记的标记位都是01,如何区分?

对,因为它们的标记位都是01,所以根据其他位(biased lock flag 偏向锁标记)区分。

如果还是不懂,一图抵千言:

干货!一文带你了解Java并发中的锁优化,让你的代码运行效率翻倍

轻量级锁执行过程:

步骤一:在代码进入同步块的时候,如果此同步对象没有被锁定(如上表所述,锁标志位为 “01” 状态)虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的 Mark Word 的拷贝(官方把这份拷贝加上了一个 Displaced 前缀,即 Displaced Mark Word),这时候线程堆栈与对象头的状态如图:

干货!一文带你了解Java并发中的锁优化,让你的代码运行效率翻倍

然后,虚拟机将使用 CAS 操作尝试将对象的 Mark Word 更新为指向 Lock Record 的指针。

步骤二( Mark Word 更新为指向 Lock Record 的指针,更新成功): 如果这个更新动作成功了,那么这个线程就拥有了该对象的锁,并且对象 Mark Word 的锁标志位 (Mark Word 的最后 2bit)将转变为 “00”,即表示此对象处于轻量级锁定状态,这时候线程堆栈与对象头的状态如图:

干货!一文带你了解Java并发中的锁优化,让你的代码运行效率翻倍

一个完整的轻量级锁获取过程,起码需要经历4次CAS操作。所以,获取轻量级锁的CAS次数总是>=4的。如果出现线程持续获取锁失败的情况,那么,轻量级锁就会执行膨胀,意思就是升级为重量级锁。且看步骤三。

步骤三(Mark Word 更新为指向 Lock Record 的指针,更新失败):如果这个更新操作失败了,虚拟机首先会检查对象的 Mark Word 是否指向当前线程的栈帧,如果只说明当前线程已经拥有了这个对象的锁,那就可以直接进入同步块继续执行,否则说明这个锁对象以及被其他线程线程抢占了。

如果有两条以上的线程争用同一个锁,那轻量级锁就不再有效,要膨胀为重量级锁,所标志的状态变为 “10”,Mark Word 中存储的就是指向重量级锁(互斥量)的指针,后面等待锁的线程也要进入阻塞状态。这时候线程堆栈与对象头的状态如图:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值