1、为什么会有锁升级
在JDK1.6之前,synchronized的性能一直没有reentrantlock性能高,主要是因为syschronized涉及到用户态和内核态的切换,这个不管是在操作系统还是硬件都是非常消耗资源的。
《经统计分析,在大部分时间一个锁都是被一个线程去获取,如果只有一个线程来尝试加锁,就是重量级锁,显然很浪费资源》
总之:锁的升级过程是为了提高多线程环境下的性能和吞吐量,减少同步操作的开销,并尽量避免线程切换的开销。Java虚拟机根据线程竞争的情况和锁的使用情况自动进行锁的升级和降级,已优化多线程程序的性能。
2、锁分类
偏向锁:偏向锁是为了解决单线程访问的场景,偏向锁允许第一个访问共享资源的线程获得锁,把线程的id存在对象头中,后续的访问可以直接获得锁,而不需要竞争。
轻量级锁:当一个或多个线程尝试获取同一个锁时,偏向锁会升级为轻量级锁。轻量级锁采用CAS(compare and swap)操作来减小锁的竞争。采用自适应自旋。
重量级锁:操作系统的调度器会介入,将竞争锁的线程挂起,直到锁被释放为止,重量级锁的开销相对较高。
[补充:]
[自适应自旋的基本思想是根据锁的争用情况,决定线程是否应该自旋等待,以及自旋等待的时间,一般情况为自旋10次。]
3、对象内存结构
对象:对象头、实例数据、对其填充位
对象头具体信息:
4、图解锁升级过程
[ 无锁 ] -> [ 偏向锁 ] -> [ 轻量级锁 ] -> [ 重量级锁 ]
在JDK8时,偏向锁默认是在程序启动后4s自动开启的,在JKD15之后默认是不开启的!
可以设置无延迟时间启动:-XX:BiasedLockingStartupDelay=0
也可以不启动偏向锁:-XX:-UseBiasedLocking = fals
5、实例演说
从序号1开始,默认4s后开启偏向锁,我们会发现序号1打印的对象头序号为:001我们的对象大小为20,内部帮我们补位来满足是8的倍数。方便操作系统进行寻址,不会有碎片组合!这个大家可以详细搜一下,这里就一带而过了哈!
此时我们睡眠6s,包装偏向锁开启成功!
我们来到序号2,开启了偏向锁,我们发现对象头序号为:101
「节点:从无锁到偏向锁切换的条件:JDK8中默认4s后开启,JDK15需要手动开启」
来到序号3和4一起说吧,当我们进行synchronized加锁时,对象的头信息中会记录上当前线程的id,下面再有加锁的,直接判断线程id是否一致,一致直接进入代码块。不一致后面再说!我们发现在序号4时,已经出了代码块,在此查询加锁的对象,信息依旧在,不会进行移除,这就是偏向,直到下一个线程把上一个替换掉!
代码里循环了三次,对象都是一样的!
「节点:在只有一个线程访问代码块的时候,对象中会记录当前线程id。」
「以上都是在一个线程来访问的情况下」
来到序号5,我们新建了一个线程来进行加锁。此时会判断当前线程id和新线程id是否一致,不一致就会认为有竞争关系,会立刻切换为轻量级锁。对象头序号为:00
「节点:当有两个线程交替获取锁时,不存在同时竞争获取锁时。」
序号6和7一起说,我们让上面序号5这个线程获取锁后睡眠3s,持续获得锁。在开启一个新的线程去竞争获取锁,此时先进行自适应CAS自旋,一般10次后一直没办法获取锁,判定为激烈竞争关系。变为重量级锁,序号7线程会进行放到阻塞队列中。对象头序号为:10
经过睡眠后,序号6在此获取对象的信息时,已经变为重量级锁!
「节点:有两个及其以上线程同时获取锁,且在自适应自旋范围内没有获取到锁」
public class LockUp {
@SneakyThrows
public static void main(String[] args) {
LockInfo lockInfo = new LockInfo();
System.out.println("1.无状态:" + ClassLayout.parseInstance(lockInfo).toPrintable());
Thread.sleep(6000);
LockInfo lock = new LockInfo();
System.out.println("2.已经开启了偏向锁模式:" + ClassLayout.parseInstance(lock).toPrintable());
for (int i = 0; i < 3; i++) {
synchronized (lock) {
System.out.println("3.偏向锁模式下,加锁状态:" + ClassLayout.parseInstance(lock).toPrintable());
}
System.out.println("4.锁释放了,加锁状态:" + ClassLayout.parseInstance(lock).toPrintable());
}
new Thread(() -> {
synchronized (lock) {
System.out.println("5.轻量级锁,加锁状态:" + ClassLayout.parseInstance(lock).toPrintable());
System.out.println("睡眠3s");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("6.轻量级锁=>重量级锁,加锁状态:" + ClassLayout.parseInstance(lock).toPrintable());
}
}).start();
Thread.sleep(1000);
new Thread(() -> {
synchronized (lock) {
System.out.println("重量级锁,加锁状态:" + ClassLayout.parseInstance(lock).toPrintable());
}
}).start();
}
}