Java锁升级的过程

锁的四种状态:无锁、偏向锁、轻量级锁、重量级锁(级别从高到低)

1.偏向锁:

为什么要引入偏向锁?

因为经过hotspot的作者的大量研究发现,大多数时候是不存在锁竞争的,常常是一个线程多次获得同一个锁,因此如果每次都要竞争锁会增大很多没有必要付出的代价,为了降低获取锁的代价,才引入了偏向锁。

偏向锁的升级

当线程1访问同步代码块并获取锁对象时。会在Java对象头和栈帧中记录偏向锁的threadID,因为偏向锁不会主动释放锁,因此以后线程1再次获取锁的时候,需要比较当前线程的threadID和Java对象头中的threadID是否一致,如果一致,则还是线程1获取锁对象,则无需调用CAS来加锁、解锁;如果不一致(其他线程如线程2要竞争锁对象,而偏向锁不会主动释放,因此存储的还是线程1的threadID),那么需要查看Java对象头中记录的线程1是否存活,如果没有存活,那么锁对象被重置为无锁状态,其他线程可以竞争设置其为偏向锁;如果存活,那么立刻查找该线程的栈帧信息,如果还是需要继续持有这个锁对象,那么暂停线程1,撤销偏向锁,升级为轻量级锁;如果线程1不再使用该锁对象,那么将锁对象状态设置为无锁状态,重新偏向新的线程。

偏向锁的取消

偏向锁是默认开启的,而且开始时间一般是比应用程序启动慢几秒,如果不想有这个延迟,那么可以使用-XX:BiasedLockingStartUpDelay=0;

如果不想要偏向锁,那么可以通过-XX:-UseBiasedLocking=false来设置;

2.轻量级锁

为什么要引入轻量级锁?

轻量级锁考虑的是竞争锁对象的线程不多,而且线程持有锁的时间也不长的情景。因为阻塞线程需要CPU从用户态切换到内核态,代价较大,如果刚刚阻塞不久这个锁就被释放了,那这个代价就有点得不偿失了,因此这个时候就干脆不阻塞这个线程,让它自旋等待锁释放。

轻量级锁什么时候升级为重量级锁?

线程1获取轻量级锁时会先把锁对象的对象头MarkWord复制一份到线程1的栈帧中创建用于存储锁记录的空间(称为DisplacedMarkWord),然后使用CAS把对象头中的内容替换为线程1存储的锁记录(DisplacedMarkWord)的地址;

如果线程1复制对象头的同时(在线程1CAS之前),线程2也准备获取锁,复制的对象头到线程2的锁记录空间中,但是在线程2CAS的时候,发现线程1已经把对象头换了,线程2CAS失败,那么线程2就尝试使用自旋锁来等待线程1释放锁。

但是如果自旋的时间太长也不行,因为自旋是需要消耗CPU的,因此自旋的次数是有限制的,如果自旋次数达到了限制线程1还没有释放锁,或者线程1还在执行,线程2还在自旋等待,这时候又有一个线程3来竞争这个锁对象,那么这时候轻量级锁就会膨胀为重量级锁。重量级锁把除了拥有锁的线程都阻塞,防止CPU空转。

注意:为了避免无用的自旋,轻量级锁一旦膨胀为重量级锁就不会再降级为轻量级锁了;偏向锁升级为轻量级锁也不能再降级为偏向锁。一句话就是,锁升级的过程是不可逆的。但是偏向锁可以重置为无锁的状态。

几种锁的优缺点(偏向锁、轻量级锁、重量级锁)

锁状态优点缺点适用场景
偏向锁加锁解锁无需额外的消耗,和非同步方法时间相差纳秒级别如果竞争的线程多,那么会带来额外的锁撤销的消耗基本没有线程竞争锁的同步场景
轻量级锁竞争的线程不会阻塞,使用自旋,提高程序响应速度如果一直不能获取锁,长时间的自旋会造成CPU的消耗适用于少量线程竞争锁对象,且线程持有锁的时间不长,追求响应速度的场景
重量级锁        线程竞争不使用CPU自旋,不会导致CPU空转导致消耗CPU资源线程阻塞,响应时间长很多线程竞争锁,且锁的持有时间长,追求吞吐量的场景

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值