简介

锁和同步是java多线程编程中非常常见的使用场景。为了锁定多线程共享的对象,Java需要提供一定的机制来实现共享对象的锁定,从而保证一次只有一个线程能够作用于共享对象。当第二个线程进入同一个区域的时候,必须等待第一个线程解锁该对象。

JVM是怎么做到的呢?为了实现这个功能,java对象又需要具备什么样的结构呢?快来一起看看吧。

java对象头

Java的锁状态其实可以分为三种,分别是偏向锁,轻量级锁和重量级锁。

在Java HotSpot VM中,每个对象前面都有一个class指针和一个Mark Word。 Mark Word存储了哈希值以及分代年龄和标记位等,通过这些值的变化,JVM可以实现对java对象的不同程度的锁定。

还记得我们之前分享java对象的那张图吗?

javaObject对象的对象头大小根据你使用的是32位还是64位的虚拟机的不同,稍有变化。这里我们使用的是64位的虚拟机为例。

Object的对象头,分为两部分,第一部分是Mark Word,用来存储对象的运行时数据比如:hashcode,GC分代年龄,锁状态,持有锁信息,偏向锁的thread ID等等。

在64位的虚拟机中,Mark Word是64bits,如果是在32位的虚拟机中Mark Word是32bits。

第二部分就是Klass Word,Klass Word是一个类型指针,指向class的元数据,JVM通过Klass Word来判断该对象是哪个class的实例。

我们可以看到对象头中的Mark Word根据状态的不同,存储的是不同的内容。

其中锁标记的值分别是:无锁=001,偏向锁=101,轻量级锁=000,重量级锁=010。

java中锁状态的变化

为什么java中的锁有三种状态呢?其本质原因是为了提升锁的效率,因为不同情况下,锁的力度是不一样的。

通过设置不同的锁的状态,从而可以不同的情况用不同的处理方式。

下图是java中的锁状态的变化图:

上面的图基本上列出了java中锁状态的整个生命周期。接下来我们一个一个的讲解。

偏向锁biased locking

一般来说,一个对象被一个线程获得锁之后,很少发生线程切换的情况。也就是说大部分情况下,一个对象只是被一个对象锁定的。

那么这个时候我们可以通过设置Mark word的一定结构,减少使用CAS来更新对象头的频率。

为了实现这样的目标,我们看下偏向锁的Mark word的结构:

当偏向线程第一次进入同步块的时候,会去判断偏向锁的状态和thread ID,如果偏向锁状态是1,并且thread ID是空的话,将会使用CAS命令来更新对象的Mark word。

设置是否偏向锁=1,锁标记=01,线程ID设置为当前锁定该对象的线程。

下一次该对象进入同步块的时候,会先去判断锁定的线程ID和当前线程ID是否相等,如果相等的话则不需要执行CAS命令,直接进入同步块。

如果这个时候有第二个线程想访问该对象的同步块,因为当前对象头的thread ID是第一个线程的ID,跟第二个线程的ID不同。

如果这个时候线程1的同步块已经执行完毕,那么需要解除偏向锁的锁定。

解除锁定很简单,就是将线程ID设置为空,并且将偏向锁的标志位设为0,

如果这个时候线程1的同步块还在执行,那么需要将偏向锁升级为轻量级锁。

轻量级锁thin lock

先看下轻量级锁的结构:

可以看到Mark word中存放的是栈中锁记录的指针和锁的标记=00。

如果对象现在处于未加锁状态,当一个线程尝试进入同步块的时候,会将把对象头和当前对象的指针拷贝一份,放在线程的栈中一个叫做lock record的地方。

然后JVM通过CAS操作,将对象头中的指针指向刚刚拷贝的lock record。如果成功,则该线程拥有该对象的锁。

实际上Lock Record和Mark word形成了一个互相指向对方的情况。

下次这个线程再次进入同步块的时候,同样执行CAS,比较Mark word中的指针是否和当前thread的lock record地址一致,如果一致表明是同一个线程,可以继续持有该锁。

如果这个时候有第二个线程,也想进入该对象的同步块,也会执行CAS操作,很明显会失败,因为对象头中的指针和lock record的地址不一样。

这个时候第二个线程就会自旋等待。

那么第一个线程什么时候会释放锁呢?

轻量级锁在线程退出同步块的时候,同样需要执行CAS命令,将锁标记从00替换成01,也就是无锁状态。

重量级锁

如果第二个线程自旋时间太久,就会将锁标记替换成10(重量级锁),并且设置重量级锁的指针,指向第二个线程,然后进入阻塞状态。

当第一个线程退出同步块的时候,执行CAS命令就会出错,这时候第一个线程就知道锁已经膨胀成为重量级锁了。

第一个线程就会释放锁,并且唤醒等待的第二个线程。

第二个线程被唤醒之后,重新争夺锁。

我们看下重量级锁的结构:

三种锁状态的不同

偏向锁,轻量级锁和重量级锁到底有什么不同了?

这里总结一下,偏向锁下次进入的时候不需要执行CAS命令,只做线程ID的比较即可。

轻量级锁进入和退出同步块都需要执行CAS命令,但是轻量级锁不会阻塞,它使用的是自旋命令来获取锁。

重量级锁不使用自旋,但是会阻塞线程。

好了,小伙伴们对于锁的状态变化有什么疑问吗?欢迎留言。

本文作者:flydean程序那些事

本文链接:http://www.flydean.com/jvm-object-lock-synchronization/

本文来源:flydean的博客

欢迎关注我的公众号:程序那些事,更多精彩等着您!

JVM系列之:对象的锁状态和同步的更多相关文章

  1. JVM系列——从菜鸟到入门

    持续更新系列. 参考自<深入理解Java虚拟机>.<Java性能权威指南>.<分布式Java应用基础与实践>. Java的内存结构 JVM系列——运行时数据区 JV ...

  2. jvm系列 (三) ---锁的优化

    锁的优化 目录 jvm系列(一):jvm内存区域与溢出 jvm系列(二):垃圾收集器与内存分配策略 jvm系列(三):锁的优化 我的博客目录 锁的四种状态 从低到高,只能升级不能降级 无锁状态 偏向锁 ...

  3. JVM系列一(Java内存区域和对象创建).

    一.JVM 内存区域 堆 - Heap 线程共享,JVM中最大的一块内存,此内存的唯一目的就是存放对象实例,Java 堆是垃圾收集器管理的主要区域,因此很多时候也被称为"GC堆"( ...

  4. JVM系列2:HotSpot虚拟机对象

    1.对象创建过程: ①.类加载检查:当java虚拟机遇到一条new指令时,首先会去检查该指令的参数能否在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已被加载.解析.初始化过,如果没 ...

  5. JVM系列之:详解java object对象在heap中的结构

    目录 简介 对象和其隐藏的秘密 Object对象头 数组对象头 整个对象的结构 简介 在之前的文章中,我们介绍了使用JOL这一神器来解析java类或者java实例在内存中占用的空间地址. 今天,我们会 ...

  6. jvm锁的四种状态 无锁状态 偏向锁状态 轻量级锁状态 重量级锁状态

    一:java多线程互斥,和java多线程引入偏向锁和轻量级锁的原因? --->synchronized是在jvm层面实现同步的一种机制.  jvm规范中可以看到synchronized在jvm里 ...

  7. JVM 系列(4)一看就懂的对象内存布局

    请点赞关注,你的支持对我意义重大. Hi,我是小彭.本文已收录到 GitHub · AndroidFamily 中.这里有 Android 进阶成长知识体系,有志同道合的朋友,关注公众号 [彭旭锐] ...

  8. jvm系列 (四) ---强、软、弱、虚引用

    java引用 目录 jvm系列(一):jvm内存区域与溢出 jvm系列(二):垃圾收集器与内存分配策略 jvm系列(三):锁的优化 我的博客目录 为什么将引用分为不同的强度 因为我们需要实现这样一种情 ...

  9. JVM系列1:Java内存区域

    JVM系列主要分享自己都虚拟机的理解,我自学时的知识框架多来源于<深入理解Java虚拟机_JVM高级特性与最佳实践>这本书,感兴趣的朋友可直接去阅读这本书. 本系列暂定有3部分,它们是学习 ...

随机推荐

  1. js语法基础入门(1.2)

    1.4.查找元素的方法 1.4.1.查找元素的方法 JavaScript可以去操作html元素,要实现对html元素的操作,首选应该找到这个元素,有点类似于css中的选择器 html代码: <d ...

  2. 报错Connection refused: connect

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明.本文链接:https://blog.csdn.net/qq_34266804/article/d ...

  3. ASP.NET MVC 中解决Session,Cookie等依赖的方式

    原文:https://blog.csdn.net/mzl87/article/details/90580869 本文将分别介绍在MVC中使用Filter和Model Binding两种方式来说明如何解 ...

  4. 二进制小数和IEEE浮点标准

    二进制小数 首先复习进位计数制的要素: 数码:用来表示进制数的元素.比如 二进制数的数码为:0,1 十进制数的数码为:0,1,2,3,4,5,6,7,8,9 十六进制数的数码为:0,1,2,3,4,5 ...

  5. 每日一题 - 剑指 Offer 39. 数组中出现次数超过一半的数字

    题目信息 时间: 2019-06-29 题目链接:Leetcode tag: 数组 哈希表 难易程度:简单 题目描述: 数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字. 假设数组是非空的 ...

  6. C++中vector和堆的常用使用方法&例题:数据流中的中位数

    vector常用函数: (1)a.size();//返回a中元素的个数: (2)a.push_back(5);//在a的最后一个向量后插入一个元素,其值为5 (3)a[i]; //返回a的第i个元素, ...

  7. nodejs gulp如何获取参数

    比如执行gulp test 如果需要获取test,使用命令process.argv即可 如果执行gulp test --module aaaa,这句话表示的意思执行gulp test,顺带参数modu ...

  8. ie浏览器不支持多行隐藏显示省略号

    平时在写页面过程中,相信大家都遇到过文本显示多行后用省略号代替的问题,来看看代码: p{ display: -webkit-box; overflow: hidden; text-overflow: ...

  9. Instrction Arrangement UDH 4109 拓扑排序 or 最长路

    题目描述 Ali has taken the Computer Organization and Architecture course this term. He learned that ther ...

  10. MRCTF 2020-“TiKi小组”

    题目状态: OPEN - 正在试图解这道题CLOSED - 这道题还没有打开SOLVED - 解决了!鼓掌撒花! 赛事信息 Flag格式:MRCTF{}起止时间:2020-03-27 18:00:00 ...