Synchronized锁升级过程 学习笔记
Posted 若曦`
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Synchronized锁升级过程 学习笔记相关的知识,希望对你有一定的参考价值。
1. 对象的存储方式
要明白Synchronized锁升级过程前,我们要先知道对象在堆内存中的存储模型;
堆内存中的对象分为了三块,对象头、实例变量和填充字节;
对象头
对象头主要包含两个部分 1. Mark Word (标记字段) 2. Klass Pointer(类型指针)
Mark Word(标记字段)
Mark Word就是Synchronized锁实现的关键,在后续会详细介绍
Klass Pointer(类型指针)
用于指向.Class类模板信息的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例(也就是即指向方法区类的模版信息)
实例变量
存放类的属性数据信息,包括父类的属性信息,这部分内存按4字节对齐
填充数据
虚拟机要求对象起始地址必须是8字节的整数倍。填充数据不是必须存在的,仅仅是为了字节对齐,没有其他的作用
2. Mark Word与Synchronized锁的关系
在知道了对象的存储模型后,我们知道了对象头中,有一块区域叫做MarkWork(标记字段),这个字段存储的其实就是Synchronized锁对象
当线程去访问堆内存对象的时候,会先判断这个MarkWork字段,识别当前对象的锁的状态
Mark Word的数据字段
直接看可能会觉得东西很多,很繁杂,但其中我们只需要先关注最后的一个字段,也就是锁标志位
因为当线程去读取MarkWord标记字段的时候,会先判断锁标志位的信息,从而知道此时的对象是使用的什么锁
锁标志位
- 11代表没有锁
- 01 代表无锁或偏向锁
- 00 轻量级锁
- 10 重量级锁
没有锁 11
什么是没有锁呢,也就是没有使用Synchronized关键字,那么此时对象的MarkWork字段也就完全是空的,当线程读取对象的时候,发现锁标记位为11,说明不需要进行任何的判断;
无锁 001
无锁状态,其实是代表对象还未被任何线程所读取过,也就是一个纯净的状态;
当线程读取MarkWord标记字段后,发现锁标记位为01,再去看倒数第二个字段,发现为0,那么说明当前对象没有被其他线程读取过,那么自己就可以直接读取,不用执行其他任何的判断;
线程此时会将倒数第二个标记,是否是偏向锁的标记位 0 -》1 ,表示当前数据被自己所读取过;然后将自己的线程ID存储在MarkWork标记字段的头部;
此时 Synchronized锁也就升级了,从无锁变成了偏向锁 001->101
偏向锁 101
偏向锁的锁标记位和无锁一样,也是01,不同是倒数第二个字段为1,表示自己是偏向锁,且MarkWord的头部是线程的ID;
此时,当一个线程去读取对象的时候,读取到MarkWord,发现是偏向锁,于是就会将自身的线程Id,与MarkWord中存储的线程ID去进行对比;
-
如果说是相同的线程ID,那么就可以直接读取数据;
-
如果是不同的线程ID,那么就会去判断占用着的线程是否还存活(是否还在执行)如果那个线程已经销毁了,那么就会将线程ID设为自己的,表示这是自己使用的偏向锁;
-
如果说那个线程仍然存活,那么此时锁就会升级,从偏向锁升级为轻量级锁 01->00
轻量级锁 00
轻量级锁的实现,本质上也就是CAS实现的自旋锁;
假设此时是线程A占用对象,线程B处于自旋状态;那么此时线程B就会不停的自旋,尝试去获取对象;
那么什么时候自旋结束呢,一般是以下两种情况
自旋次数达到上限
当线程B自旋到了上限(默认是10次),那么此时锁就会膨胀为重量级锁10
线程C尝试竞争锁对象
如果线程B在自旋的情况下,另一个线程C进来竞争锁,那么锁同样也会膨胀为重量级锁10
重量级锁 10
重量级锁其实就是原来的Synchronized锁的实现,没有采用自旋的方式,而是悲观锁/排他锁的思想;
chronize升级为重量级锁的时候,会有一个指向互斥量(monitor对象)的指针,这个monitor对象定义了_WaitSet和_EntryList两个队列;
ObjectMonitor() {
_count = 0; //记录数
_recursions = 0; //锁的重入次数
_owner = NULL; //指向持有ObjectMonitor对象的线程
_WaitSet = NULL; //调用wait后,线程会被加入到_WaitSet
_EntryList = NULL ; //等待获取锁的线程,会被加入到该列表
}
如果在重入锁的情况下,当一个线程去竞争锁,它会被放进_EntryList队列中去竞争,如果竞争成功,_owner指针就会指向当前线程,同时_count+1,如果竞争失败,会重新回到_EntryList中。
如果一个线程被调用了wait(),那么会释放当前持有锁,_count-1且当前线程进入_WaitSet等待被唤醒
以上是关于Synchronized锁升级过程 学习笔记的主要内容,如果未能解决你的问题,请参考以下文章
Java锁synchronized关键字学习系列之轻量级锁升级
Java锁synchronized关键字学习系列之偏向锁升级