synchronized锁升级_synchronized详解以及锁的膨胀升级过程

点击上方"码之初"关注,···选择"设为星标"

与精品技术文章不期而遇

来源:www.cnblogs.com/cxiaocai/p/12189848.html

架构之路远且艰辛,但是学习的脚步始终不能停止,今天聊一聊synchronized。

synchronized是jvm内部的一把隐式锁,一切的加锁和解锁过程是由jvm虚拟机来控制的,不需要我们认为的干预,我们大致从了解锁,到synchronized的使用,到锁的膨胀升级过程三个角度来说一下synchronized。

锁的分类

java中我们听到很多的锁,什么显示锁,隐式锁,公平锁,重入锁等等,下面我来总结一张图来供大家学习使用。

2084b601a97fccb18c63b6b87bac27ee.png

这次我们主要来说我们的隐示锁,就是我们的无锁到重量级锁。

synchronized的使用

我们先来看一段简单的代码

public class SynchronizedTest {

    private static Object object = new Object();
    
    public static void main(String[] args) {
        synchronized (object){
            System.out.println("只有我拿到锁啦");
        }
    }
}

就这样synchronized就可以使用了,这样是每次去拿全局对象的object去锁住后续的代码段。我们来看一下汇编指令码

 public static void main(java.lang.String[]);
    Code:
       0: getstatic     #2                  // Field object:Ljava/lang/Object;
       3: dup
       4: astore_1
       5: monitorenter
       6: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
       9: ldc           #4                  // String 只有我拿到锁啦
      11: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      14: aload_1
      15: monitorexit
      16: goto          24
      19: astore_2
      20: aload_1
      21: monitorexit
      22: aload_2
      23: athrow
      24: return
    Exception table:
       from    to  target type
           6    16    19   any
          19    22    19   any

明显看到了两个很重要的方法monitorentermonitorexit两个方法,也就是说我们的synchronized方法加锁是基于monitorenter加锁和monitorexit解锁来操作的

724d66a4d0e6445d59adbc852637dbfe.png

我们得知是由monitorenter来控制加锁和monitorexit解锁的,我们完全可以这样来操作。上次我们说过一个unsafe类。

public class SynchronizedTest {

    private static Object obj = new Object();

    public void lockMethod(){
        UnsafeInstance.reflectGetUnsafe().monitorEnter(obj);
    }
    
    public void unLockMethod(){
        UnsafeInstance.reflectGetUnsafe().monitorExit(obj);
    }
}

就是我们上次说的unsafe那个类给我们提供了加锁和解锁的方法,这样就是实现夸方法的加锁和解锁了,但是超级不建议这样的使用,后面的AQS回去说别的方式。越过虚拟机直接操作底层的,我们一般是不建议这样来做的。

我们还可以将synchronized锁放置在方法上。例如

public class SynchronizedTest {

    private static Object object = new Object();

    public static synchronized void lockMethod() {
        System.out.println("只有我拿到锁啦");
    }
}

这样加锁是加在了this当前类对象上的。如果不加static,锁是加在类对象上的,需要注意我们用的spring的bean作用域。

并且我们的synchronized是一个可重入锁,在jvm源码中有一个数值来记录加锁和解锁的次数,所以我们是可以多次套用synchronized的

public void lockMethod(){
    synchronized(obj){
        synchronized(obj){
            System.out.println("我没报错");
        }
    }
}

synchronized到底锁了什么

还是拿上个每次加锁的时候会在对象头内记录我们的加锁信息,我们这里来说一下对象头里面都放置了什么吧。

以32位JVM内部存储结构为例

ca89179d60a80a6af9c6ede66b1ab1aa.png

由此看出对象一直是有一个位置来记录我们的锁信息的。说到这我们就可以来看一下我们锁的膨胀升级过程了。

锁的膨胀升级

我们说过了对象头的内容,接下来可以说说我们的锁内部是如何升级上锁的了。从无锁到重量级锁的一个升级过程,我们来边画图,边详细看一下。

无锁状态:

1c30cea4bf1726338142ecc6452e23e5.png

开始时应该这样的,线程A和线程B要去争抢锁对象,但还未开始争抢,锁对象的对象头是无锁的状态也就是25bit位存的hashCode,4bit位存的对象的分代年龄,1bit位记录是否为偏向锁,2bit位记录状态,优先看最后2bit位,是01,所以说我们的对象可能无锁或者偏向锁状态的,继续前移一个位置,有1bit专门记录是否为偏向锁的,1代表是偏向锁,0代表无锁,刚刚开始的时候一定是一个无锁的状态,这个不需要多做解释,系统不同内部bit位存的东西可能有略微差异,但关键信息是一致的。

偏向锁:

这时线程开始占有锁对象,比如线程A得到了锁对象。

f9fe3dc4e43b580aa8e68252eab22892.png

就会变成这样的,线程A拿到锁对象,将我们的偏向锁标志位改为1,并且将原有的hashCode的位置变为23bit位存放线程A的线程ID(用CAS算法得到的线程A的ID),2bit位存epoch,偏向锁是永远不会被释放的。

接下来,线程B也开始运行,线程B也希望得到这把锁啊,于是线程B会检查23bit位存的是不是自己的线程ID,因为被线程A已经持有了,一定锁的23bit位一定不是线程B的线程ID了

effc7cd553374cfc5e6713a95c46b099.png

然后线程B也会不甘示弱啊,会尝试修改一次23bit位的对象头存储,如果说这时恰好线程A释放了锁,可以修改成功,然后线程B就可以持有该偏向锁了。如果修改失败,开始升级锁。自己无法修改,线程B只能找“大哥”了,线程B会通知虚拟机撤销偏向锁,然后虚拟机会撤销偏向锁,并告知线程A到达安全点进行等待。线程A到达了安全点,会再次判断线程是否已经退出了同步块,如果退出了,将23bit位置空,这时锁不需要升级,线程B可以直接进行使用了,还是将23bit的null改为线程B的线程ID就可以了。

fca01343864ea67de9e5848d0a97be8b.png

轻量级锁:

如果线程B没有拿到锁,我们就会升级到轻量级锁,首先会在线程A和线程B都开辟一块LockRecord空间,然后把锁对象复制一份到自己的LockRecord空间下,并且开辟一块owner空间留作执行锁使用,并且锁对象的前30bit位合并,等待线程A和线程B来修改指向自己的线程,假如线程A修改成功,则锁对象头的前30bit位会存线程A的LockRecord的内存地址,并且线程A的owner也会存一份锁对象的内存地址,形成一个双向指向的形式。而线程B修改失败,则进入一个自旋状态,就是持续来修改锁对象。

a4882494494d5ba80202d4248d9b2cec.png

重量级锁:

如果说线程B多次自旋以后还是迟迟没有拿到锁,他会继续上告,告知虚拟机,我多次自旋还是没有拿到锁,这时我们的线程B会由用户态切换到内核态,申请一个互斥量,并且将锁对象的前30bit指向我们的互斥量地址,并且进入睡眠状态,然后我们的线程A继续运行知道完成时,当线程A想要释放锁资源时,发现原来锁的前30bit位并不是指向自己了,这时线程A释放锁,并且去唤醒那些处于睡眠状态的线程,锁升级到重量级锁。

7eaf772e9da36e4952350e0ab85315e2.png

逃逸分析

很简单的一个问题,实例对象存在哪里?到底是堆还是栈?问题我先不回答,我们先看一段代码。

public class Test {

    public static void main(String[] args) throws InterruptedException {
        System.out.println("开始");
        for (int i = 0; i             createCar();
        }
        System.out.println("结束");
        Thread.sleep(10000000);
    }


    private static void createCar() {
        Car car = new Car();
    }
}

就是我们运行一个创建对象的方法,一次性创建50万个Car对象,然后我们让我们的线程进行深度的睡眠,两个打印是为了知道我们的对象已经开始创建了和已经创建完成了。我们来运行一下。

f3cafea41e7969b3c8bc014c81d82997.png

然后运行jmap -histo命令来查看我们的线程

28e7f02de6dd04ab18be2cf85c613b7c.png

我们可以看到,car对象并没有产生50万个,别说会被GC掉对象,在运行之前我已经加了GC日志的参数-XX:+PrintGCDetails,控制台没有打印任何GC日志的。那么为什么会这样呢?我们来看一下我们的代码,由createCar代码创建了car对象,但car对象并没有被其它的方法或者线程去调用,虚拟机会认为你这对象可能只是一个实例化,并没有进行使用,这时虚拟机会给予你一个优化,就是对于可能没有使用的对象进行一次逃逸,也就是我们说到的逃逸分析。我们加入 -XX:­DoEscapeAnalysis参数再看一次。

7c74c9ce0209e37817859b97544fe23b.png

这也就是关闭了我们的逃逸分析,虚拟机就会真的为我们创建了50万个对象。也就是说开启了逃逸分析有一部分对象只是创建了线程栈上,当线程栈结束,对象也被销毁,上面的问题也就有答案了,实例对象可能存在堆上,也可能存在栈上。

感谢大家的阅读,不正确的地方,还希望大家来斧正,我们一起探讨技术问题,觉得写得好的,给个推荐,给个赞,点个关注吧,鞠躬,谢谢?。

好文!必须在看

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/567237.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

改变php二维数组的值_php如何修改二维数组中的值?

php修改二维数组中值的方法&#xff1a;1、通过【for($i 0; $i < count(Array()); $i)】语句修改&#xff1b;2、通过foreach($users as &$user)语句修改。php修改二维数组中值的方法&#xff1a;1、二维数组可以通过for($i 0; $i < count(Array()); $i)这种形式…

jlabel字怎么变化_怎样才能把字写好?详解最科学的练字方法及步骤

一&#xff1a;正确的学习之路1&#xff1a;临帖从古到今&#xff0c;临帖是学习书法最基本的方法&#xff0c;没有一个书法家是没有临过帖的。2&#xff1a;专注专注学一个书法家的字&#xff0c;专心致志&#xff0c;认真临写&#xff0c;持之以恒&#xff0c;直到形同神似。…

找不到具有指定id_JAVA如何整合es指定字段不分词搜索?

一、问题在做一个需求的时候&#xff0c;需要按照电话号码查询用户关系&#xff0c;所以我这边先讲相关信息同步到es&#xff0c;但是电话号码是加密的&#xff0c;所以显示的字符串是杂乱的&#xff0c;既有字母&#xff0c;又有斜杠等号等字符&#xff0c;在进行分词查询的时…

vant 项目_vueCli4+vant+router+vuex+移动端适配

教程的开始 各位同学请先安装node、npm、vuecli4 巴拉巴拉...废话不多说立即开始随便找个地方 cmd&#xff0c;powerShell也可以 命令行走起1. 创建vue项目 随便起个名字就叫my-vue吧&#xff0c;傻瓜式回车键就行&#xff0c;也可以根据自己的喜好选择eslint。vue create my-v…

电路结构原理_精密半波、全波整流电路结构原理图解

利用二极管(开关器件)的单向导电特性&#xff0c;和放大器的优良放大性能相结合&#xff0c;可做到对输入交变信号(尤其是小幅度的电压信号)进行精密的整流&#xff0c;由此构成精密半波整流电路。若由此再添加简单电路&#xff0c;即可构成精密全波整流电路。二极管的导通压降…

vlookup两个条件匹配_vlookup,你还是只会基础的单条件查找?

很多数据分析师在简历上都会说自己熟悉excel&#xff0c;但熟练使用excel必须要掌握的核心函数vlookup首当其冲&#xff0c;其次是sumifs、match、index等等。就算熟悉vlookup&#xff0c;很多人也只会基础的单条件查找&#xff0c;其实在工作中能用到vlookup的地方有很多&…

matlab 绘制符号函数,DAY8 MATLAB学习笔记—simulink入门、MATLAB符号函数的图形绘制...

如何打开simulink&#xff1a;启动simulink&#xff1a;先打开MATLAB软件界面第一步打开simulink第二步在command windows输入 simulink然后enter&#xff0c;等待有很多模块库第三步&#xff1a;常用的simulink库打开以后会看到simulink library browser这个界面最常用的就是s…

后盾网php多少钱_商标转让做公证花多少钱-购店网

当别人对您的商标感兴趣&#xff0c;或者您想买属于自己的商标时&#xff0c;转让商标是个好主意。但不了解商标转让流程的朋友&#xff0c;可能会担心自己对商标公证书的作用缺乏了解。所以今天我们给大家介绍一下什么是商标转让公证的用途&#xff0c;具体什么是商标公证。想…

MATLAB求线性代数的参数范围,MATLAB科学计算04(线性代数问题求解一)

文章目录特殊矩阵矩阵的基本概念求解线性方程组直接求解判定求解特殊矩阵零矩阵、1矩阵及单位矩阵生成nxn方阵&#xff1a;Azeros(n), Bones(n), Ceye(n)生成mxn矩阵&#xff1a;Azeros(m,n), Bones(m,n), Ceye(m,n)生成和矩阵B同样位数的矩阵&#xff1a;Azeros(size(B))**生成…

wsl 重启_漫谈在Windows Server 2019中安装使用WSL

熟悉IT历史的朋友一定不会忘记98蓝屏事件&#xff1a;时任微软CEO的比尔盖茨和助理 Chris Capossela在1998年春季计算机分销商展会&#xff08;COMDEX &#xff09;现场演示Windows 98的“即插即用”&#xff08;plug-and-play&#xff09;新特性时&#xff0c;经历了演示计算机…

uniapp 输入框防抖节流_拉动一下控制台大小,后台请求数量爆炸,竟是没做好防抖与节流...

点击上方☝Java编程技术乐园&#xff0c;轻松关注&#xff01;及时获取有趣有料的技术文章文章很好&#xff0c;耐心阅读&#xff0c;记得点赞和关注哦~前言最近有个朋友在面试过程中遇到一个问题&#xff1a;什么是防抖和节流&#xff1f;糟了&#xff0c;这可触碰到我的知识盲…

linux matlab runtime,linux 环境 MATLAB Runtime 安装

安装 MUTsigcv 软件时需要用到MATLAB环境&#xff0c;安装过程如下&#xff1a;环境下载&#xff1a;https://ww2.mathworks.cn/products/compiler/matlab-runtime.html安装帮助&#xff1a;https://ww2.mathworks.cn/help/compiler/install-the-matlab-runtime.html下载安装软…

智能车辆手册 pdf_SIMULINK在虚拟车辆开发方面的应用

获得pdf全文&#xff0c;朋友圈集赞30个好书推荐&#xff1a;1. Radar: 《雷达手册》<推荐理由&#xff1a;中译本在原著的基础上增加了缩略语词汇总表等5个附录&#xff0c;便于读者查阅。原著是集合当今世界雷达各方面造诣最深的专家、学者编撰而成的&#xff0c;受到全世…

无限极 php算法,无限极分类算法,对你一定有帮助

无限级分类是开发中常见的情况,也经常会在面试&#xff0c;主考官问到&#xff0c;笔试中遇到&#xff0c;因此本文对常见的无限极分类算法进行总结归纳&#xff0c;其实大多数就是迭代与递归。1.循环迭代实现$arr [1>[id>1,name>父1,father>NULL],2>[id>2,…

查看队列深度_不为人知的网络编程(十一):从底层入手,深度分析TCP连接耗时的秘密...

“ 本文作者张彦飞&#xff0c;原题“聊聊TCP连接耗时的那些事儿”&#xff0c;本次收录已征得作者同意&#xff0c;转载请联系作者。即时通讯网收录时有少许改动。本文已同步发布于52im社区&#xff1a;http://www.52im.net/thread-3265-1-1.html(1、引言对于基于互联网的通信…

几级工作台做石头高墙_创造与魔法:建筑新手入门攻略,家园建材怎么做

hello大家好&#xff0c;又见面啦&#xff0c;我是创造与魔法郁金香建筑团的建筑师&#xff0c;九梨梨众所周知&#xff0c;我是一个热衷于出各类建筑案例的人&#xff0c;玩创魔两年多&#xff0c;做了好几十个不同款式的家园吧。其实创魔这个游戏&#xff0c;建筑只是其中一个…

oracle vitu,Supply Chain Management (SCM) a Manufacturing | Oracle Česká Republika

Nebyly nalezeny ždn vsledkyVašemu vyhledvn neodpovdaj ždn vsledky.Abyste našli to, co hledte, doporučujeme vyzkoušet nsledujc postup:Zkontrolujte pravopis vašich klčovch slov ve vyhledvn.Použijte synonyma pro klčov slovo, kter jste zadali, napřk…

两步路轨迹文件位置_最新Uber ATG的轨迹预测方法LiRaNet介绍

arXiv今年10月刚刚上传论文“LiRaNet: End-to-End Trajectory Prediction using Spatio-Temporal Radar Fusion“。其特色在于&#xff0c;除了激光雷达和HD Map之外&#xff0c;该轨迹预测方法采用了雷达传感器的信息。雷达和激光雷达融合是有挑战性的&#xff0c;因为前者的低…

linux查看目录下 开头,Linux下ls如何看到.开头的文件

Linux下ls查看到.开头的文件方法&#xff1a;ls -aLinux操作系统是UNIX操作系统的一种克隆系统&#xff0c;它诞生于1991 年的10 月5 日(这是第一次正式向外公布的时间)。以后借助于Internet网络&#xff0c;并通过全世界各地计算机爱好者的共同努力&#xff0c;已成为今天世界…

增值电信服务费是什么意思_增值电信业务IDC许可证要年检吗,流程是什么

据国家相关法律法规&#xff0c;持有IDC许可证的企业每年年初都需要办理IDC年检&#xff0c;企业也应当重视IDC年检&#xff0c;否则将会面临惩罚&#xff0c;所以小编来给大家介绍云南IDC许可证年检流程。为什么要办理云南IDC许可证年检,如何办理?根据《中华人民共和国电信条…