synchronized锁的升级以及各个锁的状态对比

synchronized关键字解决的是多个线程之间访问资源的同步性,synchronized关键字可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。
而在Java6之前,synchronized属于重量级锁(悲观锁),效率低下,使用wait和notify/notifyAll来切换线程状态非常消耗系统资源。因为监视器锁(monitor)是依赖于底层的操作系统的Mutex Lock来实现的,Java的线程是映射到操作系统的原生线程之上的。而操作系统实现线程之间的切换时需要从用户态转换到内核态,这个状态之间的转换需要相对较高的时间成本。
所以在Java6之后,官方从JVM层面对synchronized做了较大优化。JDK1.6对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。

锁升级的原理

JVM优化了synchronized,当JVM检测到不同的竞争状态时会根据需要自动切换到合适的锁,这种切换就是锁的升级。而升级是不可逆的,也就是说只能从低到高,即偏向->轻量级->重量级,不能降级。
锁级别:无锁->偏向锁->轻量级锁->重量级锁

java对象头

synchronized用的锁存在于java对象头中,对象头里的Mark Word默认存储对象的HashCode、分代年龄和锁标记位。在运行期间,Mark Word里存储的数据会随着锁标记位的变化而变化。在这里插入图片描述

CAS

compareAndSwap,比较并替换,是一种实现并发算法时常用到的技术。
CAS有三个操作数:内存地址V 旧预期值A 即将要更新的目标值B。比如你要操作一个变量,他的值为A,你希望将他修改为B,这期间不会进行加锁,当你在修改的时候,你发现值仍旧是A,然后将它修改为B,如果此时值被其他线程修改了,变成了C,那么将不会进行值B的写入操作,这就是CAS的核心理论,通过这样的操作可以实现逻辑上的一种“加锁”,避免了真正去加锁。

无锁

没有对资源进行锁定,所有的修成都能访问并修改同一个资源,但同时只有一个线程能修改成功,其他修改失败的线程会不断重试直到修改成功。

偏向锁

对象的代码一直被同一线程执行,不存在多个线程竞争,该线程在后续的执行中自动获取锁,降低获取锁带来的性能开销。
当一个线程访问同步块并获取锁时,会在对象头和栈帧的锁记录里存储偏向的线程ID,以后在该线程进入的推出同步块时不需要进行CAS操作来加锁和解锁,只需测试Mark Word里线程ID是否为当前线程。如果测试成功,表示线程已经获得了锁;如果测试失败,则需要判断偏向锁的标识。标识被设置为0(表示当前是无锁状态),则使用CAS竞争锁;表示被设置为1(表示当前是偏向锁状态),则尝试使用CAS将对象头的偏向锁指向当前线程。
偏向锁只有在竞争出现才会释放锁。当其他线程尝试竞争偏向锁时,程序到达全局安全点后(没有正在执行的代码),它会查看Java对象头中记录的线程是否存活,如果没有存活,那么锁对象被重置为无锁状态,其它线程可以竞争将其设置为偏向锁;如果存活,那么立刻查找该线程的栈帧信息,如果还是需要继续持有这个锁对象,那么暂停当前线程,撤销偏向锁,升级为轻量级锁,如果线程1不再使用该锁对象,那么将锁对象状态设为无锁状态,重新偏向新的线程。
偏向锁应用的场景是一个同步代码块只有一个线程频繁访问,使用偏向锁,就不需要频繁使用CAS获取锁和释放锁,只需要简单判断对象头中记录的偏向锁的线程ID是否是当期线程的就可以了,所以偏向锁在这种场景下可以大大提升效率。

轻量级锁

当出现有两个线程来竞争锁的话, 那么偏向锁就失效了, 此时锁就会膨胀, 升级为轻量级锁。
线程在执行同步块之前,JVM会先在当前线程的栈帧中创建用于存储锁记录的空间,并将对象头的MarkWord复制到锁记录中,即Displaced Mark Word。然后线程会尝试使用CAS将对象头中的Mark Word替换为指向锁记录的指针。如果成功,当前线程获得锁。如果失败,表示其他线程在竞争锁,当前线程使用自旋来获取锁。当自旋次数达到一定次数时,锁就会升级为重量级锁。
当线程存在竞争时,偏向锁的效率就会降低,因为当多条线程竞争同一个偏向锁时,会频繁产生偏向锁的撤销,所以此时应该升级为轻量级锁,轻量级锁当线程竞争锁失败时,线程不会阻塞进入自旋,继续获取锁。

重量级锁

指当有一个线程获取锁之后,其余所有等待获取该锁的线程都会处于阻塞状态。
重量级锁通过对象内部的监视器(monitor)实现,而其中 monitor 的本质是依赖于底层操作系统的 Mutex Lock 实现,操作系统实现线程之间的切换需要从用户态切换到内核态,切换成本非常高。

synchronized优化-锁消除

消除锁是虚拟机另外一种锁的优化,这种优化更彻底,Java虚拟机在JIT编译时(可以简单理解为当某段代码即将第一次被执行时进行编译,又称即时编译),通过对运行上下文的扫描,去除不可能存在共享资源竞争的锁,通过这种方式消除没有必要的锁,可以节省毫无意义的请求锁时间,如StringBuffer的append是一个同步方法,但是在add方法中的StringBuffer属于一个局部变量,并且不会被其他线程所使用,因此StringBuffer不可能存在共享资源竞争的情景,JVM会自动将其锁消除。

锁的比较

在这里插入图片描述

参考:
synchronized锁的升级原理是什么?以及各个锁的状态对比
多线程锁的升级(膨胀)原理
Java6及以上版本对synchronized的优化

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值