java中的锁——偏向锁,轻量级锁,重量级锁

本文介绍了Java中锁的三种形态:偏向锁、轻量级锁和重量级锁。偏向锁适用于无多线程竞争的情况,减少加锁解锁的开销;轻量级锁通过自旋实现,避免线程阻塞;重量级锁使用Monitor对象,当线程竞争激烈时会导致性能下降。锁只能升级,不可降级,以保证效率。

基础知识

CAS操作
java对象头

锁的四种状态
无锁状态->偏向锁状态->轻量级锁状态->重量级锁状态
为了提高获得和释放锁的效率,锁只能升级,不能降级
因为锁升级机制,在竞争激烈时效率降低了

偏向锁

偏向锁是指一段同步代码一直被一个线程所访问(不存在多线程竞争),那么该线程会自动获取锁,降低获取锁的代价。

获得

当一个线程访问同步代码块并获取锁时,会在Mark Word里存储锁偏向的线程ID。在线程进入和退出同步块时不再通过CAS操作来加锁和解锁,而是检测Mark Word里是否存储着指向当前线程的偏向锁。如果存储,则表示线程已经获得锁。否则测试Mark Word中偏向锁的标识符是否为1(当前是否为偏向锁,1是0否),如果不是偏向锁则使用CAS竞争锁(使用CAS将对象头的偏向锁指向当前线程),如果是则尝试使用CAS将对象头的偏向锁指向当前线程。
在这里插入图片描述
引入偏向锁是为了在无多线程竞争的情况下尽量减少不必要的轻量级锁执行路径,因为轻量级锁的获取及释放依赖多次CAS原子指令,而偏向锁只需要在置换ThreadID的时候依赖一次CAS原子指令即可。

撤销

偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程不会主动释放偏向锁。
偏向锁的撤销,需要等待全局安全点(在这个时间点上没有字节码正在执行),它会首先暂停拥有偏向锁的线程,检查持有偏向锁的线程是否处于活动状态(同步代码块是否执行完),不处于活动状态则将对象头设置为无锁状态,线程可以使用CAS竞争锁。处于活动状态,则升级到轻量级锁,唤醒暂停线程继续执行。
在这里插入图片描述

关闭

偏向锁在JDK 6及以后的JVM里是默认启用的,在应用程序启动几秒后激活。当存在线程竞争时,偏向锁会提升获取锁的资源消耗,可关闭偏向锁,关闭之后程序默认会进入轻量级锁状态。
-XX:BiasedLockingStartupDelay=0
-XX:UseBiasedLocking=false

轻量级锁

当锁是偏向锁的时候,被另外的线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,从而提高性能。自旋超过一定次数升级为重量级锁(重量锁会阻塞其他线程,避免了无用的自旋)。

当多线程环境进入synchronized区域的线程没竞争时,JVM并不会马上创建对象锁,而是用轻量锁或偏向锁。一旦出现了多线程竞争时,synchronized区域的轻量锁或偏向锁都会立即升级为重量锁。

加轻量锁:虚拟机首先将在当前线程的栈帧创建一块空间用于存储锁记录(lock record),把对象头中的Mark Word复制到锁记录中,拷贝成功后,虚拟机将使用CAS操作将Mark Word更新为指向Lock Record的指针。(并将Lock Record里的owner指针指向对象的Mark Word)。

若操作成功,那就完成了轻量锁操作,把对象头里的tag改成00,表示此对象处于轻量级锁定状态。

如果失败,虚拟机首先会检查对象的Mark Word是否指向当前线程的栈帧,如果是就说明当前线程已经拥有了这个对象的锁,那就可以直接进入同步块继续执行;

否则说明多个线程在竞争锁:若当前只有一个等待线程尝试自旋获取锁,自旋超过一定的次数,或者多个线程竞争,则需要在当前对象上生成重量锁。
在这里插入图片描述

解锁:CAS使用把lock record替换回对象头里,若替换成功,则解锁完成,若替换不成功,表示在当前线程持有锁的这段时间内,其他线程也竞争过锁,锁膨胀为重量锁,释放锁,唤醒挂起线程。

重量级锁

将锁标志的状态值变为“10”,生成Monitor对象(重量锁对象),Mark Word中存储Monitor对象的地址。最后将当前线程放入排队队列中。此时除了拥有锁的线程以外,所有线程都阻塞会进入阻塞状态。

锁的比较

优点缺点适用场景
偏向锁无多线程竞争时加锁和解锁快,和执行非同步方法差不多。置换ThreadID的时候仅需一次CAS原子指令即可,避免了轻量级多次CAS操作如果线程间存在锁竞争,会带来额外的锁撤销的消耗。适用于只有一个线程访问同步块场景。
轻量级锁线程交替执行代码块(无多线程竞争,同一时间访问同一锁升级为重量级),通过CAS操作和自旋,避免线程阻塞和唤醒,提高了程序的响应速度。如果始终得不到锁竞争的线程使用自旋会消耗CPU。追求响应时间。同步块执行速度非常快。
重量级锁线程竞争不使用自旋,不会消耗CPU。线程的阻塞唤醒,需要在用户态和核心态之间转换,消耗大,响应时间缓慢。追求吞吐量。同步块执行速度较长。

锁升级时对象头变化

在这里插入图片描述

hashcode

  • 对象创建完毕后,对象头中的 hashCode 为 0
  • 第一次调用hashCode,会把对象 hashCode 写入对象头
  • 对象头中已经存在hashcode 则偏向锁不可用,直接从轻量级锁开始。没有hashcode的才可以从偏向锁开始。
  • 轻量级锁和重量级锁将hashcode从对象头中移到了lock record或者monitor中。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值