Java锁升级
锁升级
整体对象头 Mark Word 结构如下:
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 当中,这个集合的线程都会阻塞。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构