synchronized锁升级过程

1.前置知识:

1.1 JAVA对象的内存布局

            hotspot虚拟机中,普通对象在堆中的存储可以划分成三部分:对象头(包含了MarkWord和类型指针)、实例例数据和padding。

JAVA对象的内存布局

MarkWord的长度为4byte/8byte,用于存储对象自身的运行时数据,如HashCode、GC分代年龄、是否为偏向锁、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳、Monitor等。最后2位用于存储状态信息。

MarkWord不同状态下的存储结构

对于锁而言,重点关注的有两点: biased_lock和状态信息。

1.2.Monitor/ LockRecord

在重量级锁时,MarkWord中会存储指向Monitor的指针。轻量级锁加锁时,会尝试将对象的MarkWord CAS地更新到线程的栈帧的LockRecord中,并同时将LockRecord的地址保存到对象的markword内。这部分作了解,后面细说。


2. 锁升级过程

锁升级状态的四个阶段: 无锁、偏向锁、轻量级锁和重量级锁。

2.1 无锁: //TODO

2.2 偏向锁:

意思是这个锁会偏向于第一个获得它的线程,如果在接下来的执行过程中,该锁一直没有被其他的线程获取,则持有偏向锁的线程将永远不需要再进行同步。若虚拟机启用了偏向锁,当锁对象第一次被线程获取的时候,虚拟机将会把对象头中的标志位设置为“01”、把偏向模式设置为“1”,表示进入偏向模式。同时使用CAS操作把获取到这个锁的线程的ID记录在对象的Mark Word之中。如果CAS操作成功,持有偏向锁的线程以后每次进入这个锁相关的同步块时,虚拟机都可以不再进行任何同步操作。一旦出现另外一个线程去尝试获取这个锁的情况,偏向模式就马上宣告结束。根据锁对象目前是否处于被锁定的状态决定是否撤销偏向(偏向模式设置为“0”),撤销后标志位恢复到未锁定(标志位为“01”)或轻量级锁定(标志位为“00”)的状态,后续的同步操作就按照轻量级锁那样去执行。

    问题来了:1.对象头中原来存了哈希码,现在哈希码没有了,咋整?Easy, 哈希码由Object::hashCode()生成,返回对象的一致性哈希码,因此只要对象是同一个且没有重写hashCode方法,重新生成的哈希码也不变。

2.3 轻量级锁:

    轻量级锁是相对于重量级锁而言的,它设计的初衷实在没有多线程竞争的前提下,减少重量级锁的性能损耗。

2.3.1 轻量级锁加锁的实现方式

在代码即将进入同步块的时候,如果此同步对象没有被锁定(锁状态位是01),如果设置了偏向模式,检查偏向的线程是否时当前线程,如果不是,难么将膨胀为轻量级锁。虚拟机在当前线程的栈帧中创建lockRecord, 用于存储对象MarkWord的拷贝(加锁成功后存储markword)和对象的引用地址(用于锁住之后完成对象的访问定位)。

虚拟机在线程thread0的栈帧中创建了LockRecord

创建完LockRecord之后,虚拟机将使用CAS操作尝试把对象的Mark Word更新为指向当前线程中LockRecord的指针。这里CAS的比较方法是:锁标志位是否为01,如果是则更新为LockRecord地址并将标志位置位00。


尝试将MarkWord CAS地更新为指向LockRecord的指针

    如果更新成功,此时LockRecord中存放了对象的原来的markword信息,同时将对象的markword锁标志位置为00,而对象的markword则存放了持有锁的线程的LockRecord地址,如下图。如果更新失败,则表示该对象的锁已经被持有了,持有锁的线程可能是他自己,也可能是其他线程。然后虚拟机先检查对象的MarkWord是否指向当前线程的栈帧,如果是,说明当前线程已经拥有了这个对象的锁,那直接进入同步块继续执行就可以了;否则就说明这个锁对象已经被其他线程抢占了,当前线程开始自旋重试。这里自旋重试次数可以是0,也就意味着发生竞争就会膨胀到重量级锁。当然也可以是某个数,取决于jdk设计者如何考量。所以有的文章说会自旋,有的不会自旋,无非是次数是否为0的差别。


更新成功后轻量级锁状态下Markword
CAS更新成功后线程栈帧和被锁住的对象

    为什么更新失败后仍要检查对象MarkWord是否指向当前栈帧呢?原因是锁的重入。CAS更新失败有两种可能,1.它自己已经持有了该对象的锁,现在要重入。 2.其他线程持有了对象的锁。若是当前线程CAS更新了MarkWord,那么当前线程再次想要持有对象的锁时,它应该要能重入。锁重入的时候,又创建了新的LockRecord,但由于CAS更新失败,它内部并没有对象原来MarkWord的拷贝。

锁重入

    轻量级锁状态下,MarkWord指向哪个线程的栈帧,就意味着哪个线程持有了锁。

2.3.2 轻量级锁的解锁

    当退出synchronized(obj)代码块的时候,若发现有取值为null的锁记录,表示有重入。此时解锁的操作就是移除这个记录,重入次数减一(见上图)

    如果锁记录的MarkWord拷贝不为null, 则需要CAS将MarkWord恢复回对象头。如果恢复成功,则表示成功解锁。恢复失败,则表示已升级为重量级锁,进入重量级锁的解锁流程。

2.3.3锁膨胀

    当并发高,线程之间竞争激烈的时候,采用CAS自旋的方式会有问题,没获取到锁的线程长时间占用着CPU,却又没能拿到锁。时间一长,系统中自旋的线程太多,看起来cpu一直在忙,任务进度却非常缓慢。 因此自旋应当有一定的次数限制,超过次数就进入锁膨胀流程,将锁升级为重量级锁,使拿不到锁的线程进入阻塞状态。

    升级过程如图:thread1自旋获取轻量级锁失败,先为obj对象创建重量级锁Monitor,Monitor的owner指向当前持有锁的线程t0;然后将指向LockRecord的指针更改为指向Monitor的指针,将锁的状态改为10; 最后让自己进入阻塞队列中。

升级为重量级锁

2.4重量级锁

2.4.1 Monitor结构及重量级锁上锁

当对象的锁为重量级锁的时候,MarkWord存放了指向Monitor的指针,这个Monitor实际上就是对象的锁信息。它包含了:持有锁的线程,想要持有锁但被阻塞的队列EntryList以及处于waiting状态的线程。这也就不难理解为什么使用了obj.wait()的时候,会直接升级到重量级锁,因为其他状态的下没有waitSet啊,那我在那里等着被唤醒嘛。

Monitor结构

    Monitor对象被加锁的共享变量关联,在Monitor对象中记录锁的持有锁的线程,并在对象内部维护了等待持有锁的阻塞队列EntryList,若thread1执行到synchronized(obj)时,obj的锁已被其他线程获取,那么t1就进入阻塞状态,并进入阻塞队列。

线程1获取锁失败,进入阻塞队列这里有个问题:

这里有个问题,当线程1进来时,线程2正在准备持有当前monitor,但是t2又还没持有monitor,应该怎么处理呢。(我猜感觉可能大概是cas的方式?进来先判断是否owner已经不为null了,如果是则直接进入entryList,不是则cas地比较并交换,如果比较失败,则下一轮重新判断? 有懂的大神可以评论区解答一下哈)

2.4.2 重量级锁解锁

    当持有锁的线程执行完synchronized(obj)中的代码块时,释放锁。唤醒EntryList队列中的所有线程,然后这些线程开始抢占锁,抢到了就成为owner,未抢到则回到阻塞队列中。

3.锁升级过程

    一开始是无锁状态,当有线程使用的时候会升级成偏向锁,这时候是单线程状态,一旦有第二个线程竞争锁,将会升级为轻量级锁,其余线程会自旋等待,当自旋到一定次数时升级成重量级锁,这时其余线程进入等待队列,等待被唤醒。另一种升级成重量级锁的方式是,遇到wait()等待其他人notify(),会自动直接升级成重量级锁。



盗用一张图:http://www.jetchen.cn/synchronized-status/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,718评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,683评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,207评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,755评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,862评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,050评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,136评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,882评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,330评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,651评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,789评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,477评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,135评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,864评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,099评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,598评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,697评论 2 351

推荐阅读更多精彩内容