32位:

64位:

无锁:

1001001110000101111101010101110

HashCode:1237514926            十进制:1237514926 二进制:0100100 11000010 111110101 0101110
# Running 64-bit HotSpot VM.
# Objects are 8 bytes aligned.
# Field sizes by type: 8, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
# Array element sizes: 8, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]com.jmdf.world.testR.JOLTest$A object internals:OFFSET  SIZE      TYPE DESCRIPTION                               VALUE0     4           (object header)                           01 ae fa c2 (00000001 10101110 11111010 11000010) (-1023758847)4     4           (object header)                           49 00 00 00 (01001001 00000000 00000000 00000000) (73)8     4           (object header)                           b0 36 1e 17 (10110000 00110110 00011110 00010111) (387856048)12     4           (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)16     1   boolean A.flag                                    false17     7           (loss due to the next object alignment)
Instance size: 24 bytes
Space losses: 0 bytes internal + 7 bytes external = 7 bytes total

注:synchronized锁流程如下
第一步,检查MarkWord里面是不是放的自己的ThreadId ,如果是,表示当前线程是处于 “偏向锁”
第二步,如果MarkWord不是自己的ThreadId,锁升级,这时候,用CAS来执行切换,新的线程根据MarkWord里面现有的ThreadId,通知之前线程暂停,之前线程将Markword的内容置为空。
第三步,两个线程都把对象的HashCode复制到自己新建的用于存储锁的记录空间,接着开始通过CAS操作,把共享对象的MarKword的内容修改为自己新建的记录空间的地址的方式竞争MarkWord,
第四步,第三步中成功执行CAS的获得资源,失败的则进入自旋
第五步,自旋的线程在自旋过程中,成功获得资源(即之前获的资源的线程执行完成并释放了共享资源),则整个状态依然处于 轻量级锁的状态,如果自旋失败
第六步,进入重量级锁的状态,这个时候,自旋的线程进行阻塞,等待之前线程执行完成并唤醒自己
总结:
偏向锁,其实是无锁竞争下可重入锁的简单实现

轻量锁

轻量级锁也是一种多线程优化,它与偏向锁的区别在于,轻量级锁是通过CAS来避免进入开销较大的互斥操作,而偏向锁是在无竞争场景下完全消除同步,连CAS也不执行(CAS本身仍旧是一种操作系统同步原语,始终要在JVM与OS之间来回,有一定的开销)。

轻量级锁(LightweightLocking)本意是为了减少多线程进入互斥的几率,并不是要替代互斥。

它利用了CPU原语Compare-And-Swap(CAS,汇编指令CMPXCHG),尝试在进入互斥前,进行补救。

轻量级锁加锁:线程在执行同步块之前,JVM会先在当前线程的栈桢中创建用于存储锁记录的空间,并将对象头中的MarkWord复制到锁记录中,官方称为DisplacedMark Word。然后线程尝试使用CAS将对象头中的MarkWord替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。

轻量级锁解锁:轻量级解锁时,会使用原子的CAS操作来将DisplacedMark Word替换回到对象头,如果成功,则表示没有竞争发生。如果失败,表示当前锁存在竞争,锁就会膨胀成重量级锁。下图是两个线程同时争夺锁,导致锁膨胀的流程图。

在图中,提到了拷贝锁对象头的objectmark word,由于脱离了原始markword,官方将它冠以displaced前缀,即displacedmark word(置换标记字)。

这个displacedmark word是整个轻量级锁实现的关键,在CAS中的compare就需要用它作为条件。

为什么要拷贝mark word?其实很简单,原因是为了不想在lock与unlock这种底层操作上再加同步。

在拷贝完object markword之后,JVM做了一步交换指针的操作,即流程中第一个橙色矩形框内容所述。

============

将object mark word里的轻量级锁指针指向lockrecord所在的stack指针,作用是让其他线程知道,该objectmonitor已被占用(就像偏向锁中用CAS的方式将markword的id指向当前尝试获取锁的线程id,这里是将markword中的轻量级锁指针以CAS的方式尝试指向当前线程的lockrecord,这样别的线程便知道当前轻量锁已经指向别的线程了)。

lock record里的owner指针指向objectmark word的作用是为了在接下里的运行过程中,识别哪个对象被锁住了。

CAS 的是makeworld中的轻量级锁指针

================


下图直观地描述了交换指针的操作。
最后一步unlock中,我们发现,JVM同样使用了CAS来验证object markword在持有锁到释放锁之间,有无被其他线程访问。

如果其他线程在持有锁这段时间里,尝试获取过锁,则可能自身被挂起,而markword的重量级锁指针也会被相应修改。

释放锁线程视角:所以由轻量锁切换到重量锁,是发生在轻量锁释放锁的期间,之前在获取锁的时候它拷贝了锁对象头的markword,在释放锁的时候如果它发现在它持有锁的期间有其他线程来尝试获取锁了,并且该线程对markword做了修改,两者比对发现不一致,则切换到重量锁。

因为重量级锁被修改了,所有display mark word和原来的markword不一样了。

怎么补救,就是进入mutex前,compare一下obj的markword状态。确认该markword是否被其他线程持有。

此时如果线程已经释放了markword,那么通过CAS后就可以直接进入线程,无需进入mutex,就这个作用。

尝试获取锁线程视角:如果线程尝试获取锁的时候,轻量锁正被其他线程占有,那么它就会修改markword,修改重量级锁,表示该进入重量锁了。

还有一个注意点:等待轻量锁的线程不会阻塞,它会一直自旋等待锁,并如上所说修改markword。

这就是自旋锁,尝试获取锁的线程,在没有获得锁的时候,不被挂起,而转而去执行一个空循环,即自旋。在若干个自旋后,如果还没有获得锁,则才被挂起,获得锁,则执行代码。

因为自旋会消耗CPU,为了避免无用的自旋(比如获得锁的线程被阻塞住了),一旦锁升级成重量级锁,就不会再恢复到轻量级锁状态。当锁处于这个状态下,其他线程试图获取锁时,都会被阻塞住,当持有锁的线程释放锁之后会唤醒这些线程,被唤醒的线程就会进行新一轮的夺锁之争。

个人理解:
当第一个线程执行到synchronized修饰的同步块时,当前线程会会修改锁对象头的Mark world的ThreadId = 当前线程,锁标识为01,对象锁由无锁状态升级为偏向锁,当该线程再次访问该同步代码块时,只需要比较对象锁存储的线程ID是否等于当前线程,等于则无需加锁操作,直接执行代码块。如果不等,则该对象锁在偏向锁基础上,等到到了全局安全点时,撤销偏向锁,(多个线程分别把对象锁的Mark world拷贝到各自工作线程的Lock Recode工作线程锁空间中,使用CAS把当前线程ID写到工作线程锁空间中dispaced Mark world,成功后再该当前线程的地址写到锁对象的mark world中)升级为轻量级锁,

无锁 -> 偏向锁

从上图可以看到 , 偏向锁的获取方式是将对象头的 MarkWord 部分中, 标记上线程ID, 以表示哪一个线程获得了偏向锁。 具体的赋值逻辑如下:

首先读取目标对象的 MarkWord, 判断是否处于可偏向的状态(如下图)
这里写图片描述

如果为可偏向状态, 则尝试用 CAS 操作, 将自己的线程 ID 写入MarkWord

如果 CAS 操作成功(状态转变为下图), 则认为已经获取到该对象的偏向锁, 执行同步块代码 。 注意, age 后面的标志位中的值并没有变化, 这点之后会用到
补充: 一个线程在执行完同步代码块以后, 并不会尝试将 MarkWord 中的 thread ID 赋回原值 。这样做的好处是: 如果该线程需要再次对这个对象加锁,而这个对象之前一直没有被其他线程尝试获取过锁,依旧停留在可偏向的状态下, 即可在不修改对象头的情况下, 直接认为偏向成功。

如果 CAS 操作失败, 则说明, 有另外一个线程 Thread B 抢先获取了偏向锁。 这种状态说明该对象的竞争比较激烈, 此时需要撤销 Thread B 获得的偏向锁,将 Thread B 持有的锁升级为轻量级锁。 该操作需要等待全局安全点 JVM safepoint ( 此时间点, 没有线程在执行字节码) 。
如果是已偏向状态, 则检测 MarkWord 中存储的 thread ID 是否等于当前 thread ID 。

如果相等, 则证明本线程已经获取到偏向锁, 可以直接继续执行同步代码块
如果不等, 则证明该对象目前偏向于其他线程, 需要撤销偏向锁
从上面的偏向锁机制描述中,可以注意到

偏向锁的 撤销(revoke) 是一个很特殊的操作, 为了执行撤销操作, 需要等待全局安全点(Safe Point), 此时间点所有的工作线程都停止了字节码的执行。
偏向锁的撤销(Revoke)
如上文提到的, 偏向锁的撤销(Revoke) 操作并不是将对象恢复到无锁可偏向的状态, 而是在偏向锁的获取过程中, 发现了竞争时, 直接将一个被偏向的对象“升级到” 被加了轻量级锁的状态。 这个操作的具体完成方式如下:

在偏向锁 CAS 更新操作失败以后, 等待到达全局安全点。
通过 MarkWord 中已经存在的 Thread Id 找到成功获取了偏向锁的那个线程, 然后在该线程的栈帧中补充上轻量级加锁时, 会保存的锁记录(Lock Record), 然后将被获取了偏向锁对象的 MarkWord 更新为指向这条锁记录的指针。
至此, 锁撤销操作完成, 阻塞在安全点的线程可以继续执行。
偏向锁的批量再偏向(Bulk Rebias)机制
偏向锁这个机制很特殊, 别的锁在执行完同步代码块后, 都会有释放锁的操作, 而偏向锁并没有直观意义上的“释放锁”操作。

那么作为开发人员, 很自然会产生的一个问题就是, 如果一个对象先偏向于某个线程, 执行完同步代码后, 另一个线程就不能直接重新获得偏向锁吗? 答案是可以, JVM 提供了批量再偏向机制(Bulk Rebias)机制

该机制的主要工作原理如下:

引入一个概念 epoch, 其本质是一个时间戳 , 代表了偏向锁的有效性
从前文描述的对象头结构中可以看到, epoch 存储在可偏向对象的 MarkWord 中。
除了对象中的 epoch, 对象所属的类 class 信息中, 也会保存一个 epoch 值
每当遇到一个全局安全点时, 如果要对 class C 进行批量再偏向, 则首先对 class C 中保存的 epoch 进行增加操作, 得到一个新的 epoch_new
然后扫描所有持有 class C 实例的线程栈, 根据线程栈的信息判断出该线程是否锁定了该对象, 仅将 epoch_new 的值赋给被锁定的对象中。
退出安全点后, 当有线程需要尝试获取偏向锁时, 直接检查 class C 中存储的 epoch 值是否与目标对象中存储的 epoch 值相等, 如果不相等, 则说明该对象的偏向锁已经无效了, 可以尝试对此对象重新进行偏向操作。

偏向锁 -> 轻量级锁

从之前的描述中可以看到, 存在超过一个线程竞争某一个对象时, 会发生偏向锁的撤销操作。 有趣的是, 偏向锁撤销后, 对象可能处于两种状态。

一种是不可偏向的无锁状态, 如下图(之所以不允许偏向, 是因为已经检测到了多于一个线程的竞争, 升级到了轻量级锁的机制)

另一种是不可偏向的已锁 ( 轻量级锁) 状态

之所以会出现上述两种状态, 是因为偏向锁不存在解锁的操作, 只有撤销操作。 触发撤销操作时:

原来已经获取了偏向锁的线程可能已经执行完了同步代码块, 使得对象处于 “闲置状态”,相当于原有的偏向锁已经过期无效了。此时该对象就应该被直接转换为不可偏向的无锁状态。
原来已经获取了偏向锁的线程也可能尚未执行完同步代码块, 偏向锁依旧有效, 此时对象就应该被转换为被轻量级加锁的状态
轻量级加锁过程:

首先根据标志位判断出对象状态处于不可偏向的无锁状态( 如下图)

在当前线程的栈桢(Stack Frame)中创建用于存储锁记录(lock record)的空间,并将对象头中的Mark Word复制到锁记录中,官方称为Displaced Mark Word。如果在此过程中发现,
然后线程尝试使用 CAS 操作将对象头中的 Mark Word 替换为指向锁记录的指针。
如果成功,当前线程获得锁
如果失败,表示该对象已经被加锁了, 先进行自旋操作, 再次尝试 CAS 争抢, 如果仍未争抢到, 则进一步升级锁至重量级锁。

synchronized锁升级过程详解相关推荐

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

    点击上方"码之初"关注,···选择"设为星标" 与精品技术文章不期而遇 来源:www.cnblogs.com/cxiaocai/p/12189848.html ...

  2. synchronized锁升级过程及其实现原理

    本文链接:https://blog.csdn.net/wangyy130/article/details/106495180 问:为什么会有锁升级的过程呢 答:在java6以前synchronized ...

  3. synchronized 锁升级过程

    synchronized 锁升级过程就是其优化的核心:偏向锁 -> 轻量级锁 -> 重量级锁 class Test{private static final Object object = ...

  4. Synchronized锁升级过程

    Java SE 1.6 为了减少获得锁和释放锁带来的性能消耗,引入了 "偏向锁" 和 "轻量级锁":锁一共有 4 种状态,级别从低到高依次是:无锁状态.偏向锁状 ...

  5. Synchronized锁升级:无锁-> 偏向锁 -> 轻量级锁 -> 重量级锁

    一. 概述 1. Synchronized锁升级的原因 用锁能够实现数据的安全性,但是会带来性能下降.无锁能够基于线程并行提升程序性能,但是会带来安全性下降. 2. Synchronized锁升级的过 ...

  6. 锁升级过程(无锁、偏向锁、轻量级锁、重量级锁)

    文章目录 Synchronized锁升级的背景 Synchronized的性能变化 Java5之前,用户态和内核态之间的切换 为什么每个对象都可以称为一把锁? Java6开始优化Synchronize ...

  7. 存储过程没有执行完后没有释放锁_面试必问---synchronized实现原理及锁升级过程你懂吗?...

    synchronized实现原理及锁升级过程 前言: synchronized是Java内置的机制,是JVM层面的,而Lock则是接口,是JDK层面的 尽管最初synchronized的性能效率比较差 ...

  8. 在c#中用mutex类实现线程的互斥_面试官经常问的synchronized实现原理和锁升级过程,你真的了解吗...

    本篇文章主要从字节码和JVM底层来分析synchronized实现原理和锁升级过程,其中涉及到了简单认识字节码.对象内部结构以及ObjectMonitor等知识点. 阅读本文之前,如果大家对synch ...

  9. Synchronized详细介绍之锁升级过程

    Synchronized详细介绍之锁升级过程 前言 线程与进程的区别 进程 线程 区别 协程 JVM线程调度原理 JVM线程调用过程 JAVA线程与内核线程的关系 源码分析 线程状态 Synchron ...

最新文章

  1. 五轴数控转台_各式五轴加工中心的优缺点比较
  2. show controller e1
  3. Delphi程序自删除
  4. 不平等博弈问题学习记录(二)(对于超实数在博弈下左右相等的扩充)
  5. go uintptr unsafe Pointer offset() 的使用
  6. 停车管理系统汽车到达汽车离去c语言,停车场管理系统 C语言实现
  7. Attempt to save the map xxx.m failed
  8. opensuse x64下编译Ice源码(以编译c++为例)
  9. package.json---入门说明
  10. python中template是什么意思啊_Python中Template使用的一个小技巧
  11. php mysql 网站_实验六:MYSQL+PHP的网站搭建
  12. 第十篇:Map/Reduce 工作机制分析 - 数据的流向分析
  13. [转载] 基于约束放松的自适应Web服务选取方法的研究
  14. c#(asp.net/core)杂谈笔记
  15. HTML零基础,快速学习/复习
  16. Python如何设置对数log坐标系的range
  17. 2019顶级WordPress音频和视频插件
  18. GEO数据库的使用(一)
  19. 简述使用计算机对会计工作的影响,会计电算化对会计工作的影响有哪些
  20. 技术人如何写好技术文档?

热门文章

  1. 关于C# .net JavaScript跨域
  2. this和super关键字
  3. AnnotationConfigBeanDefinitionParser are only available on JDK 1.5 and higher
  4. static{}语句块详解
  5. appium学习链接记录
  6. C#多态 (小结转载)
  7. 字符串加密解密函数 (C#) (转)
  8. 命令查询每个文件文件数
  9. Spring 当一个接口多个实现时,怎么注入
  10. Bootstrap3系列:按钮组