偏向锁、轻量级锁,重量级锁膨胀过程

在JDK 1.6之前,synchonized同步方式的成本非常高,因为使用了系统调用引起的内核态与用户态切换、线程阻塞造成的线程切换等。但是后面改进了,引进了锁的四个状态,分别是无锁,偏向锁,轻量级锁,重量级锁,而且是只能逐级膨胀的。

但是我刚接触的时候一直很纠结膨胀过程,后来搞明白了,现在抽个时间总结记录一下。

首先我们要知道,这几个级别适用的情况。

  • 偏向锁:适用于只有一个线程进入同步区
  • 轻量级锁:适用于多个线程交替进入同步区
  • 重量级锁:适用于多个线程同时竞争进入同步区

这里我就不赘述有关它们三个的具体,直接讲解如何膨胀。

  1. 当一个线程(假设叫A线程)想要获得锁时,首先检查对象头中的锁标志,如果是偏向锁,则跳转到2,如果是无锁状态,则跳转到3.
  2. 检查对象头中的偏向线程id是否指向A线程,是,则直接执行同步代码块,不是则3.
  3. 使用cas操作将替换对象头中的偏向线程id,成功,则直接执行同步代码块。失败则说明其他的线程(假设叫B线程)已经拥有偏向锁了,那么进行偏向锁的撤销(因为这里有竞争了),此时执行4.
  4. B线程运行到全局安全点后,暂停该线程,检查它的状态,如果处于不活动或者已经退出同步代码块则原持有偏向锁的线程释放锁,然后A再次执行3。如果仍处于活动状态,则需要升级为轻量级锁,此时执行5.
  5. 在B线程的栈中分配锁记录,拷贝对象头中的MarkWord到锁记录中,然后将MarkWord改为指向B线程,同时将对象头中的锁标志信息改为轻量级锁的00,然后唤醒B线程,也就是从安全点处继续执行。
  6. 由于锁升级为轻量级锁,A线程也进行相同的操作,即,在A线程的栈中分配锁记录,拷贝对象头中的Mark Word到锁记录中,然后使用cas操作替换MarkWord,因为此时B线程拥有锁,因此,A线程自旋。如果自旋一定次数内成功获得锁,那么A线程获得轻量级锁,执行同步代码块。若自旋后仍未获得锁,A升级为重量级锁,将对象头中的锁标志信息改为重量级的10,同时阻塞,此时请看7.
  7. B线程在释放锁的时候,使用cas将MarkWord中的信息替换,成功,则表示无竞争(这个时候还是轻量级锁,A线程可能正在自旋中)直接释放。失败(因为这个时候锁已经膨胀),那么释放之后唤醒被挂起的线程(在这个例子中,也就是A)。

以上就是我理解的锁膨胀过程。有错误的地方,欢迎指正。

这里补充一些额外的知识

自旋锁,也就是如果持有锁的线程能很短时间内释放锁,那么竞争锁的线程就不需要进入阻塞挂起状态,而是等一会(自旋),这样能避免用户线程和内核线程的切换消耗,但是如果超过一定时间仍未得到,还是会进入阻塞。

通过自旋锁,可以减少线程阻塞造成的线程切换(包括挂起线程和恢复线程)。
1.6之后引入了自适应自旋锁,是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。

自适应自旋解决的是“锁竞争时间不确定”的问题。JVM很难感知到确切的锁竞争时间,而交给用户分析就违反了JVM的设计初衷。自适应自旋假定不同线程持有同一个锁对象的时间基本相当,竞争程度趋于稳定,因此,可以根据上一次自旋的时间与结果调整下一次自旋的时间。

各个锁适用的场景:
这里我使用一下《java并发编程艺术》中的总结

优点缺点适用场景
偏向锁加锁和解锁不需要额外的消耗,和执行非同步方法比仅存在纳秒级的差距如果线程间存在锁竞争,会带来额外的锁撤销的消耗适用于只有一个线程访问同步块场景
轻量级锁竞争的线程不会阻塞,提高了程序的响应速度如果始终得不到锁竞争的线程使用自旋会消耗CPU追求响应时间,锁占用时间很短
重量级锁线程竞争不使用自旋,不会消耗CPU线程阻塞,响应时间缓慢追求吞吐量,锁占用时间较长
  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值