锁升级过程
- 一、偏向锁
- 二、轻量级锁
- 三、重量级锁
- 四、整体流程
为什么不全部使用Synchronized、Lock等重量级锁呢?
重量级锁底层是基于操作系统的互斥锁实现的,涉及到用户态与内核态之间的切换。
一、偏向锁
如果只有一个线程A频繁的访问某一个共享资源,频繁的对其进行加锁解锁浪费系统资源。
偏向锁顾名思义,如果只有线程A,那么以后线程A访问就不需要获取锁解锁了,直接访问就行。线程判断偏向锁是否偏向自己的依据是,锁对象的MarkWord字段的前52位是否是自己的线程ID。
那假如此时有一个线程B来访问共享资源呢?这时候情况分为两种了:
1、线程A已经访问结束,此时相当于只有线程B,那么锁会偏向于线程B
2、线程A还在访问中,线程B尝试自旋一次,如果失败,锁会升级成轻量级锁,然后B再自旋。
详细流程如下:
注意 JDK15之后默认关闭了偏向锁,需要自行打开。
二、轻量级锁
轻量级锁主要适用于线程交替执行,没有竞争。
按照上一篇文章中讲到的,轻量级锁的对象头格式大致如下:
[ptr | 00] locked ptr points to real header on stack
mark word 最后两位是 00 00 00,前 62 62 62 位是指向栈帧中Lock Record的指针。
这里解释一下 轻量级锁加锁、解锁过程发生了什么:
1、加锁:
- 线程会在自己本地栈空间中创建
Lock Record
,将其obj (Object Reference)
指向锁对象,表示锁自己占用了 - 将锁对象的markword的前 62 62 62 位复制到 Lock Record的
Displaced Mark Word
字段中,保存原有的锁对象的信息。然后通过CAS尝试将自己的Lock Record
地址值放在锁对象的mark word
中,如果成功,则修改成功 - 如果已经是当前线程持有锁了,那么此次代表一次重入,设置新的
Lock Record
的obj
指向当前锁,Displaced Mark Word
字段为null - 如果都失败了,表示锁被其他线程占有了,膨胀为重量级锁。
解锁:
- 遍历线程栈,找到所有obj字段等于锁的Lock Record
- 如果Lock Record的 Displaced Mark Word为null,说明是一次重入,将obj设置为null后continue
- 如果不是null,通过CAS指令将当前Lock Record中的 Displaced Mark Word值赋值给锁的Mark Word字段,如果成功,则continue,否则膨胀为重量级锁。
轻量级锁的工作流程:
轻量级锁的自旋是有次数限制的,在JDK6之后,使用了自适应轻量级锁:
- 如果本次自旋成功,那么失败次数是当前自旋次数 + 1, 即本次成功了有理由认为下次也成功。
- 如果本次失败了,那么失败次数是当前自旋次数 - 1, 即本次失败了,有理由认为下次也是失败,减少cpu空转。
三、重量级锁
当轻量级锁也解决不了问题的时候,锁会膨胀为重量级锁
锁对象 mark word字段的前62位会放在 互斥量对象的mark word中, 然后指向互斥量对象。