不吃饭也要掌握的Synchronized锁升级过程

一、前言在面试题中经常会有这么一道面试题,谈一下synchronized锁升级过程?
之前背了一些,很多文章也说了 , 到底怎么什么条件才会触发升级,一直不太明白 。
实践是检验真理的唯一标准,今天就和大家一起实践一下,什么条件才会升级!
二、为什么会有锁升级过程?在实践之前,我们先一步步的来了解!为什么要升级呢?
在JDK1.6之前,synchronized的性能一直没有ReentrantLock性能高,主要是因为synchronized涉及到用户态和内核态的切换,这个是在操作系统和硬件是非常消耗资源的 。
经过不断的统计分析,发现大部分时间一个锁都是一个线程去获?。?如果只有一个线程来尝试加锁,就是重量级锁 , 显而浪费资源 。
【不吃饭也要掌握的Synchronized锁升级过程】「总之 , 锁的升级过程是为了提高多线程环境下的性能和吞吐量 , 减少同步操作的开销,并尽量避免线程切换的开销 。JAVA虚拟机根据线程竞争的情况和锁的使用情况自动进行锁的升级和降级,以优化多线程程序的性能 。」
此时,就引入了很多锁类型,下面我们来具体看看!
三、锁分类偏向锁:偏向锁是为了解决单线程访问的场景,偏向锁允许第一个访问共享资源的线程获得锁 , 把线程id存到对象头中,后续的访问可以直接获得锁,而不需要竞争 。
轻量级锁:当一个或多个线程尝试获取同一个锁时,偏向锁会升级为轻量级锁 。轻量级锁采用CAS(Compare and Swap)操作来减小锁的竞争 。采用自适应自旋!
重量级锁:操作系统的调度器会介入 , 将竞争锁的线程挂起,直到锁被释放为止,重量级锁的开销相对较高 。
「补充:」
「自适应自旋的基本思想是根据锁的争用情况 , 决定线程是否应该自旋等待,以及自旋等待的时间,一般情况为自旋10次 。」
四、对象内存结构我们在说锁的升级过程之前,需要了解一下对象的内存结构,因为在锁升级过程中会往对象头上进行填充信息!一个对象分为:对象头、实例数据、对其填充位三部分组成 。

不吃饭也要掌握的Synchronized锁升级过程

文章插图
我们本次主要用到对象头,我们再看一下详细的对象头信息里有什么:
不吃饭也要掌握的Synchronized锁升级过程

文章插图
五、图解锁升级过程先来一个简图:
不吃饭也要掌握的Synchronized锁升级过程

文章插图
下面引用百度上的一张详细一点的图:
不吃饭也要掌握的Synchronized锁升级过程

文章插图
我们来详细的说一下锁的升级过程,在每一个锁切换时的条件是什么?
在JDK8时,偏向锁默认是在程序启动后4s自动开启的 , 在JKD15之后默认是不开启的!
可以设置无延迟时间启动:-XX:BiasedLockingStartupDelay=0也可以不启动偏向锁:-XX:-UseBiasedLocking = false 。
直接说有点不形象,我们下面结合代码来实战,看一下具体情况!
六、实战锁升级过程为了我们能够查询对象结构 , 我们需要引入jar帮助我们查看!
1、导入依赖「注意」:不要使用高版本的,高版本不显示2进制,不好观察!
<dependency><groupId>org.openjdk.jol</groupId><artifactId>jol-core</artifactId><version>0.10</version></dependency>2、实战代码和解析我们来从序号1开始,上面也说了默认4s后开启偏向锁,我们会发现序号1打印的对象头序号为:001我们的对象大小为20 , 内部帮我们补位来满足是8的倍数 。方便操作系统进行寻址,不会有碎片组合!这个大家可以详细搜一下,这里就一带而过了哈!
不吃饭也要掌握的Synchronized锁升级过程

文章插图
此时我们睡眠6s , 包装偏向锁开启成功!
我们来到序号2,开启了偏向锁,我们发现对象头序号为:101 。
「节点:从无锁到偏向锁切换的条件:JDK8中默认4s后开启 , JDK15需要手动开启」 。
不吃饭也要掌握的Synchronized锁升级过程

文章插图
来到序号3和4一起说吧,当我们进行synchronized加锁时,对象的头信息中会记录上当前线程的id,下面再有加锁的,直接判断线程id是否一致,一致直接进入代码块 。不一致后面再说!我们发现在序号4时,已经出了代码块,在此查询加锁的对象,信息依旧在,不会进行移除,这就是偏向,直到下一个线程把上一个替换掉!


推荐阅读