Synchronized锁升级过程

一、锁升级入门

锁升级的过程就是从无锁->偏向锁->轻量级锁->重量级锁

那么想必大家都知道什么是无锁和重量级锁,但对于中间两种状态的锁的概念是比较模糊的,那这两种锁分别是什么?为什么要引入这两种锁呢?我们就从JDK1.6之前的Synchronized锁来看

Java的线程是映射到操作系统原生线程之上的,如果要阻塞或唤醒一个线程就需要操作系统介入,需要在用户态与内核态之间切换,这种切换会消耗大量的系统资源,因为用户态与内核态都有各自专用的内存空间,专用的寄存器等,用户态切换至内核态需要传递给许多变量、参数给内核,内核也需要保护好用户态在切换时的一些寄存器值、变量等,以便内核态调用结束后切换回用户态继续工作。

在JDK1.6之前,Synchronized锁的状态就只有无锁重量级锁两种状态,我们都知道重量级锁在锁的竞争比较激烈的话,性能就会下降,效率低下,它是依赖于底层操作系统的Mutex Lock(系统互斥量)去实现的,挂起线程和恢复线程都需要转入内核态去处理,阻塞或唤醒一个Java的线程需要操作系统切换CPU状态来完成,用户态与内核态的切换需要耗费处理器时间,时间成本相对比较高,这也就是早期的synchronized效率低下的原因,为此JDK1.6之后引入了偏向锁轻量级锁的概念。

二、锁升级流程

synchronized用的锁是存在Java对象头里的Mark Word中

锁升级功能主要依赖MarkWord中锁标志位和释放偏向锁标志位,这里需要有JAVA内存布局的前置知识的了解。

在HotSpot虚拟机里,对象堆内存中的存储布局可以划分为三个部分:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。

而在对象头中就包含对象标记和类元信息,对象标记也就是MarkWord

​​​​​​​

我们需要先牢记这三个锁存放的位置

偏向锁:MarkWord存储的是偏向的线程ID;

轻量锁:MarkWord存储的是指向线程栈中Lock Record的指针;

重量锁:MarkWord存储的是指向堆中的monitor对象的指针;

那么来介绍一下偏向锁、轻量级锁和重量级锁是什么?

1、偏向锁

偏向锁是在实际应用运行过程中发现:如果锁总是被同一个线程锁持有,很少发生竞争,也就是说锁总是被第一个占有他的线程所持有,这个线程就是锁的偏向线程

Hotspot的作者经过研究发现,大多数情况下:

多线程的情况下,锁不仅不存在多线程竞争,还存在锁由同一个线程多次获得的情况

偏向锁就是在这种情况下出现的,它的出现是为了解决只有在一个线程执行同步时提高性能

备注:

偏向锁会偏向于第一个访问锁的线程,如果在接下来的运行过程中,该锁没有被其他的线程访问,则持有偏向锁的线程将永远不需要触发同步。也即偏向锁在资源没有竞争情况下消除了同步语句,懒的连CAS操作都不做了,直接提高程序性能

那么锁只需要在锁第一次被拥有的时候,在Mark Word下记录偏向线程ID,这样偏向线程就会一直持有着锁,在进入和退出这段代码块的时候就只需要去判断Mark Word下的线程ID是不是偏向锁线程ID,如果是的话就直接进入同步,无需每次再进行加锁解锁的操作,减少了用户态和内核态之间的切换,减少额外的开销,性能极高。

如果和偏向线程ID不一致的情况下,就说明发生了竞争,锁已经不是总是偏向于同一个线程了,这个时候回尝试使用CAS来替换Mark Word里面的线程ID为新线程的ID。

竞争成功,表示之前的线程不存在了,MarkWord里面的线程ID为新线程的ID,锁不会升级,仍然为偏向锁;

竞争失败,这时候可能需要升级变为轻量级锁,才能保证线程间公平竞争锁

说明:java15后偏向锁已经被废除,不再使用。

2、轻量级锁

轻量级锁:多线程竞争,但是任意时刻最多只有一个线程竞争,即不存在锁竞争太过激烈的情况,也就没有线程阻塞。

说明:也就是比如A和B两个线程,锁竞争不够激烈,完全可以通过CAS就能解决,一个进一个出,就不用升级到重量级锁,轻量级锁是为了在线程近乎交替执行同步块时提高性能。

主要目的:在没有多线程竞争的前提下,通过CAS减少重量级锁使用操作系统互斥量产生的性能消耗,说白了先自旋,不行才升级成重量级锁,进入阻塞状态。

轻量级锁的加锁

JVM会为每个线程在当前线程的栈帧中创建用于存储锁记录的空间,官方成为Displaced Mark Word。若一个线程获得锁时发现是轻量级锁,会把锁的MarkWord复制到自己的Displaced Mark Word里面。然后线程尝试用CAS将锁的MarkWord替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失败,表示Mark Word已经被替换成了其他线程的锁记录,说明在与其它线程竞争锁,当前线程就尝试使用自旋来获取锁。

自旋CAS:不断尝试去获取锁,能不升级就不往上捅,尽量不要阻塞

轻量级锁的释放

在释放锁时,当前线程会使用CAS操作将Displaced Mark Word的内容复制回锁的Mark Word里面。如果没有发生竞争,那么这个复制的操作会成功。如果有其他线程因为自旋多次导致轻量级锁升级成了重量级锁,那么CAS操作会失败,此时会释放锁并唤醒被阻塞的线程。

3、重量级锁

重量级锁是有大量的线程参与锁的竞争,冲突性很高,已经是轻量级锁通过CAS无法处理的时候,就会升级成重量级锁。

重量级锁原理

Javasynchronized的重量级锁,是基于进入和退出Monitor对象实现的。在编译时会将同步块的开始位置插入monitor enter指令,在结束位置插入monitor exit指令。

当线程执行到monitor enter指令时,会尝试获取对象所对应的Monitor所有权,如果获取到了,即获取到了锁,会在Monitorowner中存放当前线程的id,这样它将处于锁定状态,除非退出同步块,否则其他线程无法获取到这个Monitor

三、总结

1、synchronized锁升级无非就是先自旋,不行再阻塞,通过锁一步步的升级,当然锁的升级也会导致性能的下降

2、偏向锁:通常适用于单线程的情况,在不存在锁竞争的时候使用偏向锁

3、轻量级锁:适用于锁竞争不激烈的情况下,完全可以通过CAS解决的情况,一个进一个出,可以通过自旋来解决,如果同步方法/代码块执行时间很短的话,采用轻量级锁虽然会占用cpu资源但是相对比使用重量级锁还是更高效

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小小小王w

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值