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锁升级过程 学习笔记的主要内容,如果未能解决你的问题,请参考以下文章

Synchronized锁升级过程 学习笔记

Synchronized锁升级过程 学习笔记

Java锁synchronized关键字学习系列之轻量级锁升级

Java锁synchronized关键字学习系列之偏向锁升级

synchronized 原理使用锁升级过程,写到我要吐血了

Java6对synchronized的优化-锁升级过程详细过程