Java中synchronized的底层实现原理
一、对象头、Mark Word、monitor、synchronized怎么关联起来
(1)首先java里面每个对象JVM底层都会为它创建一个监视器monitor,这个是JVM层次为我们保证的。这个监视器就类似一个锁,哪个线程持有这个monitor的操作权,就相当于获取到了锁
(2)其次synchronized 修饰的代码或者方法,底层会生成两条指令分别为monitorenter、monitorexit。
(3)进入synchronized的代码块之前会执行monitorenter指令,去申请monitor监视器的操作权,如果申请成功了,就相当于获取到了锁。如果已经有别的线程申请成功monitor了,这个时候它就得等着 ,等别的线程执行完synchronized里面的代码之后就会执行monitorexit指令释放monitor监视器,这样其它在等待的线程就可以再次申请获取monitor监视器了。
monitor又是个啥东西?为什么monitor能当做锁?首先既然你知道每个对象都有一个monitor监视器,那你知道每个对象是怎么和它的monitor监视器关联起来的不?
通过synchronized进行加锁,就是通过对象头的Mark Word关联起来的,里面记录着锁状态和占有锁的线程地址指针 。
当Mark Word中最后两位的锁标志位是10的时候,Mark Word的前面是monitor监视器的地址,我现在就给你画出来对象头、Mark Word 和 monitor之间的关系图(32位):
二、monitor内部结构
monitor叫做对象监视器、也叫作监视器锁,JVM规定了每一个java对象都有一个monitor对象与之对应,这monitor是JVM帮我们创建的,在底层使用C++实现的 。
其实monitor在C++底层也是某个类的对象,那个类就是ObjectMonitor,它拥有的属性也字段如下://结构体如下 ObjectMonitor::ObjectMonitor() { _header; _count ; // 非常重要,表示锁计数器,_count = 0表示还没人加锁,_count > 0 表示加锁的次数 _waiters; _recursions; _owner; // 非常重要,指向加锁成功的线程,_owner = null 时候表示没人加锁 _waitset; // wait线程的集合,在synchorized代码块中调用wait()方法的线程会被加入到此集合中沉睡,等待别人叫醒它 _waitsetLock; _responsiable; _succ; _cxq; _freenext; _entrylist; // 非常重要,等待队列,加锁失败的线程会被加入到这个等待队列中,等待再次争抢锁 _spinFreq; // 获取锁之前的自旋的次数 _spinclock; // 获取之前每次锁自旋的时间 ownerIsThread; } 3.1、monitor加锁原理
_count : 这个属性非常重要,直接表示有没有被加锁,如果没被线程加锁则 _count=0,如果_count大于0则说明被加锁了
_owner:这个属性也非常重要,直接指向加锁的线程,比如线程A获取锁成功了,则_owner = 线程A;当_owner = null的时候表示没线程加锁
_waitset:当持有锁的线程调用wait()方法的时候,那个线程就会释放锁,然后线程被加入到monitor的waitset集合中等待,然后线程就会被挂起。只有有别的线程调用notify将它唤醒。_entrylist:这个就是等待队列,当线程加锁失败的时候被block住,然后线程会被加入到这个entrylist队列中,等待获取锁。
_spinFreq:获取锁失败前自旋的次数;JDK1.6之后对synchronized进行优化;原先JDK1.6以前,只要线程获取锁失败,线程立马被挂起,线程醒来的时候再去竞争锁,这样会导致频繁的上下文切换,性能太差了。JDK1.6后优化了这个问题,就是线程获取锁失败之后,不会被立马挂起,而是每个一段时间都会重试去争抢一次,这个_spinFreq就是最大的重试次数,也就是自旋的次数,如果超过了这个次数抢不到,那线程只能沉睡了。_spinClock:上面说获取锁失败每隔一段时间都会重试一次,这个属性就是自旋间隔的时间周期,比如50ms,那么就是每隔50ms就尝试一次获取锁。
下面通过图文展示加锁过程:
(1)首先呢,没有线程对monitor进行加锁的时候是这样的:
说明:_count = 0 表示加锁次数是0,也就是没线程加锁;_owner 指向null,也就是没线程加锁
(2)然后呢,这个时候线程A、线程B来竞争加锁了,如下图所示:
(3)线程A竞争到锁,将_count 修改为1,表示加锁次数为1,将_owner = 线程A,也就是指向自己,表示线程A获取到了锁。在_count = 0,_owner = null的时候,表示monitor没人加锁,这个时候线程A和线程B同时请求加锁,也就是竞争将_count改为1。由于线程A这哥们动作比较快,它将_count改为1,获取锁成功了。它还嘚瑟了一下,同时将_onwer = 线程A,表示自己获取了锁,告诉线程B,兄弟不好意思了,是我获取了锁,我先去操作了。
既然加锁就是将_count 设置为1,同时将_owner 指向自己。那反过来推测,释放锁的时候是不是将_count 设置为 0 , 将 _owner 设置为 null 就 OK了?是的,释放锁的过程就是这么简单:
加锁和释放锁说完了,我们接下来将的是
_spinFreq、_spinclock、_entrylist
这几个东西:
上面解释字段属性的时候说_spinFreq是等待锁期间自旋的次数、_spinclock是自旋的周期也就是每次自旋多久时间、_entrylist这个就是自旋次数用完了还没获取锁,只能放到_entrylist等待队列挂起了。
让我们继续接着图来讲:
(1)首先线程B获取锁的时候发现monitor已经被线程A加锁了(2)然后monitor里面记录的_spinFreq 、spinclock 信息告诉线程B,你可以每隔50ms来尝试加锁一次,总共可以尝试10次(3)如果线程B在10次尝试加锁期间,获取锁成功了,那线程B将_count 设置为 1,_owner 指向自己表示自己获取锁成功了(4)如果10次尝试获取锁此时都用完了,那没辙了,它只能放到等待队列里面先睡觉去了,也就是线程B被挂起了_spinFreq和_spinclock 这两个monitor的属性主要是让线程自旋的时候使用的吧。entryList作用是当线程自旋次数都用完了之后,只能进入等待队列进行休眠了。4.6、轻量级锁
轻量级锁模式下,加锁之前会创建一个锁记录,然后将Mark Word中的数据备份到锁记录中(Mark Word存储hashcode、GC年龄等很重要数据,不能丢失了),以便后续恢复Mark Word使用。这个锁记录放在加锁线程的虚拟机栈中,加锁的过程就是将Mark Word 前面的30位指向锁记录地址。所以mark word的这个地址指向哪个线程的虚拟机栈中,就说明哪个线程获取了轻量级锁。就好比下面的图,线程A获取了轻量级锁,锁记录存在线程A的虚拟机栈中,然后Mark Word的前面30位存储锁记录的地址。
了解了轻量级加锁的原理之后,我们继续,来讲讲偏向锁升级为轻量级锁的过程:
(1)首先线程A持有偏向锁,然后正在执行synchronized块中的代码
(2)这个时候线程B来竞争锁,发现有人加了偏向锁并且正在执行synchronized块中的代码,为了避免上述说的线程A一直持有锁不释放的情况,需要对锁进行升级,升级为轻量级锁
(3)先将线程A暂停,为线程A创建一个锁记录Lock Record,将Mark Word的数据复制到锁记录中;然后将锁记录放入线程A的虚拟机栈中
(4)然后将Mark Word中的前30位指向线程A中锁记录的地址,将线程A唤醒,线程A就知道自己持有了轻量级锁
4.6.2、在轻量级锁模式下,多线程是怎么竞争锁和释放锁的?
(1)线程A和线程B同时竞争锁,在轻量级锁模式下,都会创建Lock Record锁记录放入自己的栈帧中
(2)同时执行CAS操作,将Mark Word前30位设置为自己锁记录的地址,谁设置成功了,锁就获取到锁
上面讲了加锁的过程,轻量级锁的释放很简单,就将自己的Lock Record中的Mark Word备份的数据恢复回去即可,恢复的时候执行的是CAS操作将Mark Word数据恢复成加锁前的样子。Java synchronized偏向锁后hashcode存在哪里?jdk8偏向锁是默认开启,但是是有延时的,可通过参数: -XX:BiasedLockingStartupDelay=0关闭延时。hashcode是懒加载,在调用hashCode方法后才会保存在对象头中。当对象头中没有hashcode时,对象头锁的状态是 可偏向( biasable,101,且无线程id)。如果在同步代码块之前调用hashCode方法,则对象头中会有hashcode,且锁状态是 不可偏向(0 01),这时候再执行同步代码块,锁直接是 轻量级锁(thin lock,00)。如果是在同步代码块中执行hashcode,则锁是从 偏向锁 直接膨胀为 重量级锁。
母乳喂养有助宝宝神经发育,但这些常见问题,你遇到过吗?当宝宝降临这个世界以后,给我们带来了无数的快乐,同时也将面临母亲生涯的第一个选择母乳还是奶粉喂养?母乳喂养的好处当然是肯定的,除了能给宝宝带来更强的免疫力更充沛的营养以及更紧密的亲
97年金州惨案后,球迷写的大连金州不相信眼泪到底有多感人?1997年的(金州惨案)。相信老一辈的球迷永远也无法忘记。97年戚务生国家队主场对卡塔尔,当时国家队菱形442后腰李铁前腰范志毅,踢了20分钟范大将军助攻高峰10,形势不错。但是这
大智慧组合选股bug解决之法文富捷字数2676字稿数8稿撰写时间4小时31分阅读时间712分钟近日发现大智慧的条件选股功能出现bug,不能多指标组合选股,一点击,便卡出,一万年无法选完。如图,放入三个指标条件
整容的鼻子会通光,被一眼看出?网友那宋祖儿的鼻子呢爱美之心,人皆有之,随着审美意识在人们心中的提高,现在有越来越多的人对追求高颜值,但也并不是每个人都天生丽质的,所以有很多人选择通过后天的努力去改变。随着医美行业的发展,很多人都会
医药行业整体逻辑梳理由于我非医药专业背景,单个药企的判断不在我能力圈范围内,我主要看的是医药行业的整体逻辑和商业模式。和以往的文章略微有点不同,以往基本上是一篇一篇独立开来,本次的文章力求描述整个逻辑
敬致全世界人类同胞敬致全世界人类同胞千秋大义创世伟业同开太平共享福荣全世界的人类同胞们全世界一切希望世界和平安宁人类和美幸福荣生的人类同胞们全世界一切不愿世界继续混乱不愿人类争战毁灭的人类同胞们全世
四川最神秘的民族,至今还施行一妻多夫制,风景秀丽值得一去在我国四川5000米的高山峡谷地区,有这样一个特殊的民族,这里的生活方式与习俗与外界形成鲜明对比,女性地位在这里高于男性,人们称这里为女王谷,更令人惊讶的是,这里实行一妻多夫制,多
三个重尾号!51525,大乐透也开始玩神秘了江西省中出4500w第22140期大乐透开出7注头奖,每注840w,其中追加4注,每注670w。下面是开奖详情大家看到开奖号码,头有没有嗡嗡的?前几天被双色球整蒙了,这次大乐透又来整一次。哈哈。即使出
隐身资助十年,神秘好心人原来是陆老师是我心中最好的老师是我心中永远的陆妈妈近日,一封署名为刘雅轩感谢信牵出了一段长达10年的师生助学故事一次家访开启10年隐身资助10年前陆肖梅在山东潍坊昌邑市文山小学担任三四年
伏羲山大峡谷旅游攻略2022年国庆,因口罩原因,只能在郑州市域内转转。听说,新密境内的伏羲山是个不错的去处,随驱车70多公里,到达伏羲山。伏羲山,原名浮戏山,中华文明发源地,位于河南省郑州市西南新密市
流量秘籍在泰国旅游生活流量不够用?看过来,这里有无限流量哦文章内部分素材(含图片)来自网络,若侵权请联系我们及时删除出发去泰国前,选择一张信号稳定网速快的电话卡是非常必要的,不然怎么发时时更新微博和朋友圈?上网难,流量贵,一直以来都是在泰