文章目录

  • Java锁synchronized关键字学习系列之CAS和对象头
    • 锁的分类和定义
    • CAS
    • synchronized关键字
    • 对象头
      • Mark Word
  • 源代码

Java锁synchronized关键字学习系列之CAS和对象头

关于java锁的问题,一直想找时间学习一下,但是去年一整年包括今年都太颓了感觉工作久之后还有经历了一些不开心的事情之后学习的热情就不是很足了。但是人不学习就会退步,所以还是坚持一下吧。不说这么多废话了…开始吧。

谈到java锁,肯定是学习java的每个人都绕不开的问题。一谈到并发编程很自然的就会想到锁。本文只是简单粗略的去了解和学习关于Synchronized关键字的一些简单应用,比如涉及到的对象头,锁升级之类的。并没有涉及到非常深入底层的实现。毕竟我个人目前能力有限,而且篇幅会比较长,所以这篇文章只能说是Synchronized的简单了解和学习。

Java锁还可以按照底层实现分为两种。一种是由JVM提供支持的Synchronized锁,本文主要探讨的就是Synchronized关键字。另一种是JDK提供的以AQS为实现基础的JUC工具,如ReentrantLock,ReadWriteLock,以及CountDownLatch,Semaphore,CyclicBarrier等。这些如果以后有机会当然也会深入的去学习和记录(又是一个坑)。

锁的分类和定义

  1. 自旋锁:是指当一个线程在获取锁的时候,如果锁已经被其他线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环(自旋锁其实是基于CAS的一种锁实现,通过不断循环的做CAS操作。可能有的小伙伴不知道啥是CAS,一会下文会简单的解释一下)
  2. 乐观锁:假定没有冲突,再修改数据时如果发现数据和之前获取的不一致,则读最新数据,修改后重试修改(乐观锁的核心操作就是CAS)
  3. 悲观锁:假定一定会发生并发冲突,同步所有对数据的相关操作,从读数据就开始上锁(Synchronized就是悲观锁
  4. 独享锁:给资源加上独享锁,该资源同一时刻只能被一个线程持有(如JUC中的写锁)
  5. 共享锁:给资源加上共享锁,该资源可同时被多个线程持有(如JUC中的读锁)
  6. 可重入锁:线程拿到某资源的锁后,可自由进入同一把锁同步的其他代码(即获得锁的线程,可多次进入持有的锁的代码中,如Synchronized就是可重入锁
  7. 不可重入锁:线程拿到某资源的锁后,不可进入同一把锁同步的其他代码
  8. 公平锁:争夺锁的顺序,获得锁的顺序是按照先来后到的(如ReentrantLock(true))
  9. 非公平锁:争夺锁的顺序,获得锁的顺序并非按照先来后到的(如Synchronized就是非公平锁)

参考:Java锁-Synchronized深层剖析

CAS

前面提到了CAS操作,那我们这里简单提一个概念CAS,全名就是CompareAndSwap,比较并替换,是一种实现并发算法时常用到的技术。这里涉及到的就是两个操作,比较然后再交换,跟乐观锁的操作是一样的,所以乐观锁的核心操作其实就是CAS。

CAS需要有3个操作数:内存地址V,旧的预期值A,即将要更新的目标值B

比如你要操作一个变量,他的值为A,你希望将他修改为B,这期间不会进行加锁,当你在修改的时候,你发现值仍旧是A,然后将它修改为B,如果此时值被其他线程修改了,变成了C,那么将不会进行值B的写入操作,这就是CAS的核心理论,通过这样的操作可以实现逻辑上的一种“加锁”,避免了真正去加锁 。

当然CAS也存在一些问题比如ABA的问题,还有一些比较深入的知识,这里就不花太多篇幅去讲述了,以后有机会可能会专门去深入学习一下(又是一个坑)。如果有特别感兴趣的小伙伴可以参考一下其他资料。

参考:

锁开销优化以及 CAS 简单说明

原子操作CAS(Compare And Swap)

CAS原理分析及ABA问题详解

CAS原理分析

synchronized关键字

前面都是铺垫了,现在开始我们来研究一下这个Synchronized关键字吧,为啥用这么一个关键字我们就可以实现锁呢?

首先我们要知道synchronized是悲观锁,独享锁,可重入锁。这些我们在前面锁的分类和定义也多少了解了。

然后我们还知道Synchronized的使用方式有很多种,可以看下图。这里参考了:并发编程篇:synchronized的使用以及原理

那我们现在可以回答这两个问题了,synchronized关键字锁的是什么?锁的是代码还是锁的是对象?

答:锁的是对象

对象头

既然我们知道锁的是对象,那么我们就想知道锁是如何存储的?

我们知道一切皆对象。锁就是一个对象,那么这个对象里面的结构是怎么样的呢,锁对象里面都保存了哪些信息呢?

在Hotspot 虚拟机中,对象在内存中的存储布局,可以分为三个区域:

  1. 对象头(Header)
  2. 实例数据(Instance Data)
  3. 对齐填充(Padding)

那么锁的信息存在那个地方呢?我们直接给结论吧,锁的信息是存放在对象头里的。

那什么是对象头呢?我们可以引用OpenJDK的专业术语的原文:

object header: Common structure at the beginning of every GC-managed heap object. (Every oop points to an object header.) Includes fundamental information about the heap object’s layout, type, GC state, synchronization state, and identity hash code. Consists of two words. In arrays it is immediately followed by a length field. Note that both Java objects and VM-internal objects have a common object header format.

然后对象头里面包含了两个部分的信息,第一部分是mark word,第二部分是klass pointer

具体的解释我们也引用OpenJDK的专业术语的原文

mark word: The first word of every object header. Usually a set of bitfields including synchronization state and identity hash code. May also be a pointer (with characteristic low bit encoding) to synchronization related information. During GC, may contain GC state bits.

klass pointer: The second word of every object header. Points to another object (a metaobject) which describes the layout and behavior of the original object. For Java objects, the “klass” contains a C++ style “vtable”.

简单总结就是Mark Word,用于存储自身的运行时数据,如:HashCode,GC分代年龄,锁标记、偏向锁线程ID等
,所以我们说的锁信息就是记录在对象头中的Mark Word。而Klass Pointer是类型指针,即对象指向它的类元信息,虚拟机通过这个指针来确定这个对象是哪个类的实例。

参考:http://openjdk.java.net/groups/hotspot/docs/HotSpotGlossary.html

Mark Word

那接下来我们肯定是要研究一下Mark Word的结构了,在32位的虚拟机和64位的虚拟机中存储的结构不太一样。

32位虚拟机中:

64位虚拟机中:

|-----------------------------------------------------------------------------------------------------------------|
|                                             Object Header(128bits)                                              |
|-----------------------------------------------------------------------------------------------------------------|
|                                   Mark Word(64bits)               |  Klass Pointer(64bits) |      State         |
|-----------------------------------------------------------------------------------------------------------------|
| unused:25|identity_hashcode:31|unused:1|age:4|biase_lock:1|lock:2 | OOP to metadata object |      Nomal         |
|-----------------------------------------------------------------------------------------------------------------|
| thread:54|      epoch:2       |unused:1|age:4|biase_lock:1|lock:2 | OOP to metadata object |      Biased        |
|-----------------------------------------------------------------------------------------------------------------|
|                     ptr_to_lock_record:62                 |lock:2 | OOP to metadata object | Lightweight Locked |
|-----------------------------------------------------------------------------------------------------------------|
|                    ptr_to_heavyweight_monitor:62          |lock:2 | OOP to metadata object | Heavyweight Locked |
|-----------------------------------------------------------------------------------------------------------------|
|                                                           |lock:2 | OOP to metadata object |    Marked for GC   |
|-----------------------------------------------------------------------------------------------------------------|

通过上面的图我们可以知道64位的虚拟机中Mark Word占了64bits,也就是8个字节,Klass Pointer占了64bits也是8个字节。

现在我们普遍都是使用64位的虚拟机了,所以我们这里主要以64位虚拟机为主。

我们现在来看看一个对象的对象头是长怎样的吧?验证一下是不是我们上面看到的结果。

我们可以借助jol包来看看一个实例的对象头是怎样的。

首先我们来引入相关依赖

    <dependency><groupId>org.openjdk.jol</groupId><artifactId>jol-core</artifactId><version>0.8</version></dependency>

然后接下来我们就可以分析一个对象的实例了,我们首先分析第一个对象

class A {private boolean flag;
}
 public static void main(String[] args) {//===================开启指针压缩的结果//Klass是64bits(8个字节)但是这儿只有4个字节,是因为我们开启了指针压缩A a = new A();System.out.println(ClassLayout.parseInstance(a).toPrintable());//有对齐填充/*org.example.A object internals:OFFSET  SIZE      TYPE DESCRIPTION                               VALUE0     4           (object header)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)4     4           (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)8     4           (object header)                           48 72 06 00 (01001000 01110010 00000110 00000000) (422472)12     1   boolean A.flag                                    false13     3           (loss due to the next object alignment)Instance size: 16 bytesSpace losses: 0 bytes internal + 3 bytes external = 3 bytes total*/
}

我们可以设置虚拟机参数-XX:-UseCompressedOops来关闭指针压缩,然后再来看看结果。

通过上面我们可以知道一个对象中确实存在三部分:对象头(Header), 实例数据(Instance Data),对齐填充(Padding)。但是这三部分都是必须的吗?

我这里直接给出解答吧,对象头一定有,但是实例数据和对齐填充数据有可能没有。

我这里直接给出例子还有一些打印出来的对象头结果,看一看大家就知道了。

class A {private boolean flag;
}class B {private int count;
}class C {}
public static void main(String[] args) {//===================开启指针压缩的结果//Klass是64bits(8个字节)但是这儿只有4个字节,是因为我们开启了指针压缩A a = new A();System.out.println(ClassLayout.parseInstance(a).toPrintable());//有对齐填充/*org.example.A object internals:OFFSET  SIZE      TYPE DESCRIPTION                               VALUE0     4           (object header)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)4     4           (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)8     4           (object header)                           48 72 06 00 (01001000 01110010 00000110 00000000) (422472)12     1   boolean A.flag                                    false13     3           (loss due to the next object alignment)Instance size: 16 bytesSpace losses: 0 bytes internal + 3 bytes external = 3 bytes total*/B b = new B();System.out.println(ClassLayout.parseInstance(b).toPrintable());//没有对齐填充/*org.example.B object internals:OFFSET  SIZE   TYPE DESCRIPTION                               VALUE0     4        (object header)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)8     4        (object header)                           e0 f2 0e 00 (11100000 11110010 00001110 00000000) (979680)12     4    int B.count                                   0Instance size: 16 bytesSpace losses: 0 bytes internal + 0 bytes external = 0 bytes total*/C c = new C();System.out.println(ClassLayout.parseInstance(c).toPrintable());//没有实例数据/*org.example.C object internals:OFFSET  SIZE   TYPE DESCRIPTION                               VALUE0     4        (object header)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)8     4        (object header)                           e8 74 0e 00 (11101000 01110100 00001110 00000000) (947432)12     4        (loss due to the next object alignment)Instance size: 16 bytesSpace losses: 0 bytes internal + 4 bytes external = 4 bytes totalProcess finished with exit code 0*///============关闭指针压缩的结果 -XX:-UseCompressedOops/*org.example.A object internals:OFFSET  SIZE      TYPE DESCRIPTION                               VALUE0     4           (object header)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)4     4           (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)8     4           (object header)                           90 5c f1 e9 (10010000 01011100 11110001 11101001) (-370058096)12     4           (object header)                           f6 01 00 00 (11110110 00000001 00000000 00000000) (502)16     1   boolean A.flag                                    false17     7           (loss due to the next object alignment)Instance size: 24 bytesSpace losses: 0 bytes internal + 7 bytes external = 7 bytes totalorg.example.B object internals:OFFSET  SIZE   TYPE DESCRIPTION                               VALUE0     4        (object header)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)8     4        (object header)                           a0 df 5a eb (10100000 11011111 01011010 11101011) (-346366048)12     4        (object header)                           f6 01 00 00 (11110110 00000001 00000000 00000000) (502)16     4    int B.count                                   020     4        (loss due to the next object alignment)Instance size: 24 bytesSpace losses: 0 bytes internal + 4 bytes external = 4 bytes totalorg.example.C object internals:OFFSET  SIZE   TYPE DESCRIPTION                               VALUE0     4        (object header)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)8     4        (object header)                           00 e4 5a eb (00000000 11100100 01011010 11101011) (-346364928)12     4        (object header)                           f6 01 00 00 (11110110 00000001 00000000 00000000) (502)Instance size: 16 bytesSpace losses: 0 bytes internal + 0 bytes external = 0 bytes totalProcess finished with exit code 0*/}

接下来我们就可以回答几个问题了。

  1. 锁的是对象的话,那么改变了对象中的什么?
    答:改变了对象的对象头。

  2. 对象的大小是固定的吗?
    答:不是固定的,如果是固定的话,就不会有内存溢出的问题了。

  3. 对象的组成部分?
    答:对象的实例数据,大小是不固定的;对象的对象头,大小的固定的。还有一个是对齐数据(可有可无,64位的JVM,对象的大小是8 byte的整数倍,不够会填充对齐的数据)

我们继续研究一下Mard word的部分,看那看那Mard word还能存放些什么?

 public static void main(String[] args) {Object o = new Object();System.out.println(ClassLayout.parseInstance(o).toPrintable());System.out.println(Integer.toHexString(o.hashCode()));System.out.println(ClassLayout.parseInstance(o).toPrintable());}

结果:

java.lang.Object object internals:OFFSET  SIZE   TYPE DESCRIPTION                               VALUE0     4        (object header)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)8     4        (object header)                           00 10 00 00 (00000000 00010000 00000000 00000000) (4096)12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total74ad1f1f
java.lang.Object object internals:OFFSET  SIZE   TYPE DESCRIPTION                               VALUE0     4        (object header)                           01 1f 1f ad (00000001 00011111 00011111 10101101) (-1390469375)4     4        (object header)                           74 00 00 00 (01110100 00000000 00000000 00000000) (116)8     4        (object header)                           00 10 00 00 (00000000 00010000 00000000 00000000) (4096)12     4        (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

这里简单说明一下匿名偏向锁。

当JVM启用了偏向锁模式(JDK6以上默认开启),新创建对象的Mark Word中的Thread Id为0,说明此时处于可偏向但未偏向任何线程,也叫做匿名偏向状态(anonymously biased)。

我们可以通过虚拟机参数去关闭掉这个偏向锁模式, 运行时带上命令-XX:-UseBiasedLocking
然后我们来看看结果。

关于偏向锁和匿名偏向状态可以参考一下:

偏向锁

(二)偏向锁详解

之前我们介绍Mard word中会存放hashCode,但是当我们new一个Object的时候,并没有看到哪里存放了hashCode,那我们继续看看调用了o.hashCode()发生了什么,这个hashCode是存放在哪里的?

我们就不继续深入研究了,本人能力也有限哈哈,上面这些只是为了告诉大家,Mard word在不同的锁标志位下,他存放的数据不同,这样可以最大有效的利用。当然有错误的地方也希望大家能指出,这只是本人学习后的结果。

篇幅有限,本文主要简单入门和介绍一下CAS和对象头,《Java锁synchronized关键字学习系列》会继续更新,之后会学习一下关于锁升级。

源代码

https://gitee.com/cckevincyh/java_synchronized_learning

Java锁synchronized关键字学习系列之CAS和对象头相关推荐

  1. Java并发——Synchronized关键字和锁升级,详细分析偏向锁和轻量级锁的升级

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

  2. 从分布式锁角度理解Java的synchronized关键字

    分布式锁 分布式锁就以zookeeper为例,zookeeper是一个分布式系统的协调器,我们将其理解为一个文件系统,可以在zookeeper服务器中创建或删除文件夹或文件.设D为一个数据系统,不具备 ...

  3. Java锁-Synchronized深层剖析

    Java锁-Synchronized深层剖析 前言 Java锁的问题,可以说是每个JavaCoder绕不开的一道坎.如果只是粗浅地了解Synchronized等锁的简单应用,那么就没什么谈的了,也不建 ...

  4. java锁-synchronized

    java锁-synchronized 详细讨论java中synchronized关键字底层加锁原理和应用. 文章目录 java锁-synchronized 前言 1. 原理 1.1 monitoren ...

  5. Java的synchronized关键字:同步机制总结

    不久前用到了同步,现在回过头来对JAVA中的同步做个总结,以对前段时间工作的总结和自我技术的条理话.JAVA的synchronized关键字能够作为函数的修饰符,也可作为函数内的语句,也就是平时说的同 ...

  6. java 死锁 内存消耗_详解Java中synchronized关键字的死锁和内存占用问题

    先看一段synchronized 的详解: synchronized 是 java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码. 一.当两个并 ...

  7. Java I/O系统学习系列二:输入和输出

    编程语言的I/O类库中常使用流这个抽象概念,它代表任何有能力产出数据的数据源对象或者是有能力接收数据的接收端对象."流"屏蔽了实际的I/O设备中处理数据的细节. 在这个系列的第一篇 ...

  8. Java:这是一份全面 详细的 Synchronized关键字 学习指南

    前言 在Java中,有一个常被忽略 但 非常重要的关键字Synchronized 今天,我将详细讲解 Java关键字Synchronized的所有知识,希望你们会喜欢 目录 1. 定义 Java中的1 ...

  9. Java中 synchronized 关键字的理解

    synchronized 关键字的理解 在Java中,synchronized 是一个重量级的控制并发的关键字. 这个关键字可以保证并发过程所必须的"原子性","可见性& ...

  10. 【Java】synchronized关键字笔记

    Java Synchronized 关键字 壹. Java并发编程存在的问题 1. 可见性问题 可见性问题是指一个线程不能立刻拿到另外一个线程对共享变量的修改的结果. 如: package Note. ...

最新文章

  1. Python 极简实现 IoU
  2. 网页版登录入口_企业微信网页版怎么登录?企业微信客户端和网页版有什么区别?...
  3. Kotlin实现LeetCode算法题之Median of Two Sorted Arrays
  4. Dotnet洋葱架构实践
  5. 终极JPA查询和技巧列表–第2部分
  6. Socket TCP和UDP的区别
  7. 53. PHP 伪静态(2)
  8. cruzer php sandisk 闪迪u盘量产工具_闪迪u3量产工具下载 SanDisk Cruzer Micro(闪迪u盘量产工具) V1.0 官方免费版(附使用说明) 下载-脚本之家...
  9. 软件测试自学还是培训?
  10. 全面的关于OLAP数仓总结
  11. 编解码学习笔记(三):Mpeg系列——Mpeg 1和Mpeg 2
  12. 360浏览器(极速)如何导出保存的账号密码
  13. 8个有用的表单构建工具,你一定要使用并收藏好
  14. Centos7下SVN服务端搭建以及hook应用
  15. pycharm跑代码时警告libpng warning: iCCP: known incorrect sRGB profile
  16. 【秘鲁收款】秘鲁外贸收款Pago Efectivo支付
  17. MongoDB语法案例
  18. python支付宝自动支付_python 调用支付宝支付
  19. 批量剪辑视频,添加图片水印
  20. HashMap面试常问问题

热门文章

  1. 微信公众平台开发(14)--标签管理与用户标签管理
  2. 怎么有效的管理微信群?分享3点有用的经验
  3. 三组计算机局域网组网方案,怎么建立一个可以互相联机的局域网
  4. 【油猴插件】用Tampermonkey来实现百度云满速下载和批量离线
  5. adb安装apk到智能TV上
  6. PLSQL入门与精通(第56章:用PLSQL读写文件)
  7. 广告学计算机平面设计(1)形考5,精编国家开放大学电大专科《计算机平面设计》网络课形考任务1答案.docx...
  8. Python图像(字母数字)识别
  9. 赛码行测题库_行测数字推理题库
  10. 2018-7月最新QQ坦白说破解方法(亲测有效!)