Synchronized锁优化过程

锁优化的升级方向:

在这里插入图片描述
在介绍锁优化过程之前需要了解对象头的相关知识。

对象头(以32位虚拟机为例):

普通对象头包含信息如下:

在这里插入图片描述

  • Mark Word:由于存储对象自身的运行时数据,包括哈希码、GC分代年龄、锁状态标志等;
  • Klass Word:类型指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
数组对象:

在这里插入图片描述

数组对象比普通对象多了一个 记录数组长度信息 的部分(array length)。

Mark Word 结构如下:

在这里插入图片描述

tip:

  • biased_lock 表示偏向锁标志位
  • State中:Normal表示无锁状态,Biased表示偏向锁状态,Lightweight Locked 表示轻量锁状态,Heavyweight Locked 表示重量锁状态。

重量级锁:

Monitor对象:每个Java对象都可以关联一个Monitor对象,Monitor监视器源码由C++编写,在虚拟机的ObjectMonitor.hpp文件中。

当使用Synchronized给对象上重量级锁时,该对象头的Mark Word中就被设置为指向Monitor对象的指针。即变为 Heavyweight Locked状态中的ptr-to-heavyweight-monitor。

多线程竞争锁的过程:

在这里插入图片描述

  • 初始状态下,Monitor中的Owner为Null;
  • 当Thread-2执行Synchronized(obj)加锁操作时,会首先检查Owner是否为Null,如果为Null,则将Owner置为Thread-2;
  • 在Thread-2持有锁的过程中,如果有Thread-3,Thread-4,Thread-5执行加锁操作,也会先判断是Monitor的Owner是否为Null;发现Owner已经为Thread-2了,则进入EntryList中进入阻塞状态;
  • Thread-2释放锁后,会唤醒EntryList中的等待线程,让它们竞争锁。

轻量级锁

轻量级锁的使用场景:如果一个对象虽然有多个线程需要加锁,当加锁时间是分开的,则可以用轻量级锁来优化。

轻量级锁加锁过程:
  • 每个线程在其栈帧中都会创建一个所记录对象,所记录对象包含了锁记录地址、对象引用(记录加锁对象的地址);

在这里插入图片描述

  • 让锁记录对象中的Object Reference指向锁对象,并且尝试使用cas操作将锁对象的MarkWord信息和Locked Recoder中的locked record 地址 00

在这里插入图片描述

  • 如果成功,则对象头中存储了locked record 地址 00,表示有该线程给对象加锁;

    在这里插入图片描述

  • 如果cas失败,有两种情况:

    • 如果是其他线程已经持有了该Object的轻量级锁,表明有竞争,进入锁膨胀;
    • 如果是自己发生了锁重入操作,则在增加一条锁记录,lock record被设为null。
      在这里插入图片描述
  • 当执行解锁操作时,如果有取值为null的Lock Record,表示有重入,这是重置锁记录,即重入计数减一;

    在这里插入图片描述

  • 当解锁时,锁记录值不为null,这时使用cas将Mark Word的值恢复给对象头。

    • 成功,则解锁成功;
    • 失败,则说明轻量级锁进行了锁膨胀升级为重量级锁,进入重量级锁的解锁操作。

锁膨胀过程:

  • 当Thread-1 执行加锁操作时,Thread-1 已经对锁对象加了轻量级锁:

在这里插入图片描述

  • 这时Thread-1加轻量级锁失败,进入锁膨胀流程
  • 为锁对象申请Monitor锁,将锁对象的Mark Word设置为指向Monitor的指针;
  • Monitor中的Owner设置为Thread-0;
  • 然后自己进入Monitor的EntryList中等待。

在这里插入图片描述

  • 当Thread-0 执行解锁操作时,会按照轻量级锁的解锁步骤(即使用CAS操作尝试恢复锁对象的Mark Word),失败。此时会进入重量级锁的解锁流程,即通过Monitor地址找到Monitor,然后将Owner对象置为null。

自旋锁:

锁竞争时,还可使用自旋锁来进行优化,如果可以获得资源,即此时持锁线程已经推出了同步块,释放了锁,则自选成功;当前线程不用进入阻塞。

如果锁自旋失败,则升级为重量级锁。

自旋锁默认大小是10次, 可通过-XX:PreBlockSpin修改。

偏向锁:

由于轻量级锁每次重入时,都需要进行CAS操作,对性能有损耗;

Java6 中引入了偏向锁来优化这一问题,实现原理:只有第一次使用CAS将线程ID设置到Mark Word头,之后每次锁重入时,发现线程ID是自己,则不用执行CAS操作。

在这里插入图片描述
参考资料:《黑马程序员-JAVA并发讲义》《高并发编程》

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值