Java并发编程—锁升级


————————————————————————————————————

锁升级原理

  • 锁一开始并不是进入到重量级状态,一开始是在推广阶段是偏向锁,再升级到轻量级锁,再升级到重量级锁。在推广阶段请求较少,用偏向锁就能满足要求

synchronized锁升级原理:
在锁对象的对象头里面有一个threadid字段,在第一次访问的时候threadid为空, jvm 让其持有偏向锁,并将threadid 设置为其线程id,再次进入的时候会先判断。threadid是否与其线程id一致,如果一致则可以直接使用此对象,如果不一致,则升级偏向锁为轻量级锁,通过自旋循环一定次数来获取锁,执行一定次数之后,如果还没有正常获取到要使用的对象,此时就会把锁从轻量级升级为重量级锁!此过程就构成了synchronized锁的升级。

锁的升级的目的

锁的升级的目的:
锁升级是为了减低了锁带来的性能消耗。在Java 6之后优化 synchronized的实现方式,使用了偏向锁升级为轻量级锁再升级到重量级锁的方式,从而减低了锁带来的性能消耗。

锁的四种状态

上面讲到锁有四种状态,并且会因实际情况进行膨胀升级,其膨胀方向是:
无锁——>偏向锁——>轻量级锁——>重量级锁,并且膨胀方向不可逆。

偏向锁

一句话总结它的作用: 减少统一线程获取锁的代价。在大多数情况下,锁不存在多线程竞争,总是由同一线程多次获得,那么此时就是偏向锁。

  • 优点: 加锁和解锁不需要额外的消耗,和执行非同步方法相比仅存在纳秒级别的差距
  • 缺点: 如果线程之间存在竞争,会带来额外的锁撤销的消耗
  • 使用场景: 适用于一个线程访问同步块的场景
  • 核心思想: 如果一个线程获得了锁,那么锁就进入偏向模式,此时Mark word的结构也就变为偏向锁结构,当该线程再次请求锁时,无需再做任何同步操作,即获取锁的过程只需要检查Mark word的锁标记位为偏向锁以及当前线程ID等于Mark word的ThreadID即可,这样就省去了大量有关锁申请的操作。

轻量级锁

  • 轻量级锁是由偏向锁升级而来,当存在第二个线程申请同一个锁对象时,偏向锁就会立即升级为轻量级锁。
  • 注意这里的第二个线程只是申请锁,不存在两个线程同时竞争锁,可以是一前一后地交替执行同步块。
  • 自旋: 没有竞争到资源的锁,在极短的时间内查看一次资源
  • 好处: 加锁的速度快,能立刻查看到资源被解锁并加上锁
  • 缺点: 没竞争到资源的线程也会自旋,会浪费cpu开销,损害cpu利用率
  • 使用场景: 自旋锁适合线程较少,少量并发的操作,因为这样浪费的自旋操作少

重量级锁

  • 重量级锁是由轻量级锁升级而来,当同一时间有多个线程竞争锁时,锁就会被升级成重量级锁,此时其申请锁带来的开销也就变大。
  • 流程: 竞争成功的加锁,竞争失败的进入阻塞队列不参与竞争,cpu全力执行竞争成功的任务,这样cpu利用率比较高;释放锁之后会发一个通知,阻塞队列的线程出来进入就绪队列,再一次进行竞争,竞争失败的再进入阻塞队列。
  • 好处: 竞争失败的不会浪费cpu,并发量多的时候,使用重量级锁,cpu浪费率比较低,整体性能更好一些;重量级锁几乎很少导致cpu浪费
  • 缺点: 线程阻塞,响应时间慢。
  • 使用场景: 适合线程很多,并发很多,追求吞吐量,同步块或者同步方法执行时间较长的场景。

轻量级锁和重量级锁的对比

  • 轻量级锁竞争的线程不会阻塞,提高程序响应速度;竞争不到线程的锁也会自旋,会消耗cpu;适合追求响应时间快或同步块执行速度快的场景。
  • 重量级锁线程竞争失败的会阻塞,不会自旋,不会消耗cpu;线程阻塞后,之前竞争成功的线程在锁释放后需要通知被阻塞的线程,响应时间变慢;适合追求吞吐量或同步块执行速度较长的场景。

偏向锁的设置

开启偏向锁的方式:

—个对象创建时:

  • 如果开启了偏向锁(默认开启),那么对象创建后,markword值为0x05即最后3位为101,这时它的thread、epoch、age都为0。
  • 偏向锁是默认是延迟的,不会在程序启动时立即生效,如果想避免延迟,可以加VM参数-xx:BiasedLockingStartupDelay=e来禁用延迟。
  • 如果没有开启偏向锁,那么对象创建后,markword值为0x01即最后3位为001,这时它的 hashcode,age都为0,第一次用到hashcode时才会赋值。
    禁用偏向锁:
    VM 参数 -XX:-UseBiasedLocking
    特殊情况:
    调用 对象的hashcode方法时,偏向锁也会被禁用. 这是因为调用了对象的 hashCode,但偏向锁的对象 MarkWord 中存储的是线程 id(54位),如果调用 hashCode(31位) 会导致偏向锁被撤销。
    撤销 - 其它线程使用对象:
    当有其它线程使用偏向锁对象时,会将偏向锁升级为轻量级锁。
    批量撤销:
    当撤销偏向锁阈值超过 40 次后,jvm 会这样觉得,自己确实偏向错了,根本就不该偏向。于是整个类的所有对象 都会变为不可偏向的,新建的对象也是不可偏向的。

自旋锁自旋多少次竞争不到后会升级到重量级锁?

  • Java6之前:默认10次或者cpu核数的一半
  • Java6之后:根据同一个线程上次自旋的时间决定

为什么有了自旋锁还需要重量级锁?

自旋是消耗cpu性能的,如果时间过长或者自旋线程过多,cpu会被大量消耗重量级锁中有队列waitSet

偏向锁是否一定比自旋锁效率高?

  • 单线程的时候偏向锁效率高
  • 多个线程的时候偏向锁会涉及锁撤销,消耗资源,这时自旋锁效率高
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值