Java锁升级

锁升级

整体对象头 Mark Word 结构如下:
img

1. 自旋锁与自适应自旋

1.1 自旋锁

如果物理机器有一个以上的处理器或者处理器核心,能让两个或以上的线程同时并行执行,我们就可以让后面请求锁的那个线程“稍等一会”,但不放弃处理器的执行时间,看看持有锁的线程是否很快就会释放锁。为了让线程等待,我们只须让线程执行一个忙循环(自旋),这项技术就是所谓的自旋锁。

1.2 自适应自旋

自旋等待不能代替阻塞,自旋等待本身虽然避免了线程切换的开销,但它是要占用处理器时间的,所以如果锁被占用的时间很短,自旋等待的效果就会非常好,反之如果锁被占用的时间很长,那么自旋的线程只会白白消耗处理器资源,而不会做任何有价值的工作,这就会带来性能的浪费。

由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定的。如果在同一个锁对象上,自旋等待刚刚成功获得过锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋也很有可能再次成功,进而允许自旋等待持续相对更长的时间。

2. 偏向锁

只会锁对象只会被一个线程使用,会使用对象哈希码的存储空间来存储线程ID,但是对象已经计算过一致性哈希码后,它就再也无法进入偏向锁状态。其他线程来使用时,会先自旋等待一会,如果锁还没释放就自旋失败升级为轻量级锁。

  • 作用:减少 check,避免 cas 带来的性能消耗,每次只对比锁对象中的线程ID。

3. 轻量级锁

不同时间错过时间来加锁。当有其他线程来参与时,先自旋等待一会,如果失败就会升级为轻量级锁,其中使用 CAS 将 Mark Word 中的存储地址会存储到线程栈帧中的 Lock record,而栈帧中会存储 Mark Word。虚拟机首先会检查对象的Mark Word是否指向当前线程的栈帧,如果是,说明当前线程已经拥有了这个对象的锁,那直接进入同步块继续执行就可以了,否则就说明这个锁对象已经被其他线程抢占了。如果出现两条以上的线程争用同一个锁的情况,那轻量级锁就不再有效,必须要膨胀为重量级锁。

4. 重量级锁

当其膨胀成重量级锁后,其他竞争的线程进来就不会自旋了,而是直接阻塞等待,并且 Mark Word 中的内容会变成一个监视器(monitor)对象,用来统一管理排队的线程。

  • EntryList:ContentionQueue 中有资格的线程会被移动到这里,相当于进行一轮初筛,进入的线程会阻塞。
  • Owner:拥有当前 monitor 对象的线程,即持有锁的那个线程。
  • WaitSet:当 Owner 线程调用 wait() 方法被阻塞之后,会被放到这里。当其被唤醒之后,会重新进入 EntryList 当中,这个集合的线程都会阻塞。

5. 整体流程如下

img

参考博客:https://juejin.cn/post/7072533669377736711#heading-3

posted @   煽风想要点火  阅读(154)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示