java并发相关(二)—— Synchronized的锁升级机制
一、Synchronized锁四个阶段概述
java中synchronize锁分为以下四个阶段:
- 无锁
- 偏向锁
- 轻量级锁
- 重量级锁
其中偏向锁和轻量级锁是从java1.6开始引入。各阶段之间的切换,如下图:
从图中会发现,其实偏向锁是可以变成无锁的,这看似不符合我们认知中的锁可以升级不可以降级。单这种降级的本质,其实是偏向锁->偏向锁的一个过程。
二、Synchronized的锁升级机制
2.1、无锁到偏向锁:
我们知道,Synchronized修饰的方法被调用前,其对象初始状态是处于无锁状态的,其锁标记位为01,此时当线程a调用此方法时,会通过CAS自旋,替换mark words。
偏向锁是默认开启的,而且开始时间一般是比应用程序启动慢几秒,如果不想有这个延迟,那么可以使用-XX:BiasedLockingStartUpDelay=0;
如果不想要偏向锁,那么可以通过-XX:-UseBiasedLocking = false来设置;
2.2、偏向锁到偏向锁:
由于偏向锁线程1获取锁后,不会主动修改对象头,所以哪怕此线程1实际已消亡,之前加锁对象的对象头还是保持偏向锁状态。这个时候线程2想要进入同步方法,他会去查看线程1是否还存活,如果已经消亡,则把对锁定对象的对象头恢复成无锁,然后重复无锁->偏向锁的过程。
同时如果线程1未消亡,但是其栈帧信息中不在需要此持有这个锁对象,也会进行一次偏向锁->偏向锁的过程。
2.3、偏向锁到轻量级锁:
2.2的情况中,如果线程2需要进入同步方法,线程1还持有这个对象,那么就会进入偏向锁->轻量级锁的过程。
此时线程2进行cas替换失败,会修改对象头,升级为轻量级锁,同时开启自旋,重复尝试替换。
2.4、轻量级锁到重量级锁:
轻量级锁替换失败到达一定次数(默认为10)后,轻量级锁升级为重量级锁。
需要注意,如果线程2自旋期间,有线程3也需要访问同步方法,则立刻由轻量级锁膨胀为重量级锁
java1.6中,引入了自适应自旋锁,自适应意味着自旋 的次数不是固定不变的,而是根据前一次在同一个锁上自 旋的时间以及锁的拥有者的状态来决定。 如果在同一个锁对象上,自旋等待 刚刚成功获得过锁,并 且持有锁的线程正在运行中,那么虚拟机就会认为这次自 旋也是很有可能再次成功,进而它将允许自旋等待持续相 对更长的时间。
关于重量级锁,我们之前说过其本质就是操作对象内部的监视器(monitor),这一点我们会在下一篇文章中进行详细分析。