10.对象头、Mark Word、monitor、synchronized怎么关联起来?

2023年 9月 26日 119.9k 0

老王:开讲啦,开讲啦,小陈快来上课,今天我们就来探讨一下synchronized底层到底是怎么加锁的?

小陈:哈哈,这个我可等了好久了。

从我接触java开始,就知道了java里面有synchronized这个关键字,是用来加锁的,但是它底层是怎么加锁的我一直没搞明白。

看了一些文章,说通过一个monitor监视器还有什么monitorenter、monitorexit这两条执行来进行加锁和释放锁的,但是具体底层一点的细节我就不知道了,一直是我单身二十几年的一个遗憾啊......

老王:哦,听你这么说,看来你还研究过啊,那你来说说怎么通过monitor和monitorenter和monitorexit来加锁和释放锁的?

小陈:好啊,我结合之前看过的一些文章,画个图来说一下我的理解:

image.png

(1)首先java里面每个对象JVM底层都会为它创建一个监视器monitor,这个是JVM层次为我们保证的。这个监视器就类似一个锁,哪个线程持有这个monitor的操作权,就相当于获取到了锁

(2)其次synchronized 修饰的代码或者方法,底层会生成两条指令分别为monitorenter、monitorexit。

(3)进入synchronized的代码块之前会执行monitorenter指令,去申请monitor监视器的操作权,如果申请成功了,就相当于获取到了锁。

如果已经有别的线程申请成功monitor了,这个时候它就得等着,等别的线程执行完synchronized里面的代码之后就会执行monitorexit指令释放monitor监视器,这样其它在等待的线程就可以再次申请获取monitor监视器了。

老王:你说的大致正确,但是表面了一点,这个很多文章都讲到了。但是其实synchronized底层加锁其实涉及很多细节的东西。

小陈:嗯嗯,我看很多文章基本都是只是讲到了通过monitorenter、monitorexit指令去获取monitor的操作权,也就是获取锁,但是到底是怎么获取的?monitor又是个啥东西?为什么monitor能当做锁? 这些更底层的细节我就不知道了。

老王:哈哈,我会讲得通俗一点,尽量让你能听得懂......

老王:首先既然你知道每个对象都有一个monitor监视器,那你知道每个对象是怎么和它的monitor监视器关联起来的不?

小陈:额额额,这个不懂......

老王:说起这个对象和monitor的关联关系,我首先给你讲一下java对象的结构

JAVA对象结构

老王:假如说有一个Test类如下:

public class Test {
    private int a = 8;
    private ArrayList list = new ArrayList();
}

老王:那么Test对象结构如下图所示:

image.png

一个java实例对象由三部分组成,分别是:

对象头:对象头由Mark Word 和 一个指向一个类对象的指针组成。

实例数据:存放这个实例的一些属性信息,比如有的属性是基本类型,那就直接存储值;如果是对象类型,存放的就是一个指向对象的内存地址。

对其填充:主要是补齐作用,JVM对象的大小比如是8字节的整数倍,如果 (对象头 + 实例变量 )不是8的整数倍,则通过对齐填充来补齐。

比如 (对象头 + 实例变量)  部分的大小是20个字节,不是8的整数倍,那么对齐填充这里就会补上4个字节,使得(对象头 + 实例变量 + 对其填充)= 24字节,为8的整数倍。

老王:上面就是一个对象的结构,这个小陈你听懂了吗?

小陈:有点懂,又有点不懂,能不能再画个图说一下,对象头,实例变量里面是啥东西?

老王:没问题,比如说Test test = new Test(),我就来再画个图细说一下test对象的结构:

image.png

如上图所示:

(1)对象头:由Mark Word 和一个指向类对象的指针组成

(2)实例变量:记录这个对象哪些属性,如果是基本类型,直接就记录值了;如果是对象类型,则记录一下对象在堆内存的地址,方便以后找到它来使用

(3)对其填充:这块东西就是为了对象的大小满足8的整数倍,进行补齐的,别的鸟用没有

老王:小陈,我这么说,你理解了没?

小陈:哇塞,牛逼啊,你这么说整个对象的结构一目了然......

小陈:老王啊,上面那张图,我还有一个问题,那个Mark Word到底是个啥东西?

老王:Mark Word啊,那就是我们后面讲解的重点了,我们通过synchronized进行加锁,就是通过Mark Word关联起来的。

老王:为了讲清楚Mark Word,我写了一篇Mark Word的自述,让它自己说下它是干啥用的。

JVM世界的Mark Word自述

大家好,我是来自JVM世界的Mark Wold,是一个32位的数据结构,存在于对象头里面,大致结构如下:

image.png

大家都知道,计算机是由0和1组成的,每bit位不是0就是1,所以一个bit位能表示两种不同的意思,所以啊,我具有32个bit位,能表示的意思非常多。

JVM就根据Mark Word上32bit的值不同,把我设计为一个多功能的复用器,在bit标志位不同的时候表示的意思也不一样,我前面30bit位可能表示的意思不一样,但是最后2个bit表示的都是锁模式,下面我用表格说明一下我的作用:

image.png

(1)首先我的最后两位,也就是锁标志位,分别标识处于不同的锁模式;倒数第3位是偏向锁标志
(2)当我的偏向锁标志是0,锁标志位是01,也就是最后3位是001的时候,我表示无锁模式。作为Mark Word的我就是记录的数据就是对象的hashcode 和 GC的年龄

image.png

(3) 当我的偏向锁标志是1,锁标志是01,也就是最后三位是101的时候,处于偏向锁模式,我作为Mark Word这个时候记录的数据就是是获取偏向锁的线程ID、Epoch、对象GC年龄:

image.png

(4)当我的锁标志位是00的时候,表示处于轻量级锁模式。我会把锁记录放在加锁的线程的虚拟机栈空间中,所以这种情况下,锁记录在哪个线程虚拟机栈中,就表示所在线程就获取到了锁。

然后我作为Mark Word记录的数据就是就指向那个锁记录地址就好了,这个锁记录地址在哪个线程中,就表示哪个线程获取到了轻量级锁。

image.png

(5)当我锁标志位是10的时候,表示处于重量级锁模式,这个时候就说明竞争激烈了,处于重量级锁模式了,由于使用重量级加锁不是我的职责范围,是我的哥们monitor的职责,我这里有它的地址,你们去那里找他吧。

这个是我作为Mark Word 记录的数据就是我哥们monitor的地址,你们有加锁的需求直接根据我提供的这个地址找到monitor,找它加锁就好了。

image.png

老王:小陈,这就是这一节要讲的JAVA对象结构以及Mark Word,你听懂了吗?

小陈:懂了,懂了,牛逼啊,老王。感觉你把Mark Word的裤子都给扒了啊......

image.png

老王:混小子,有你这么说话的......

小陈:嘿嘿,老王,Mark Word里面的结构我是看懂了,但是我看好多人说底层可以通过Mark Word 可以进行锁升级,无锁、偏向锁、轻量级锁、重量级锁、重量级锁中的自旋等,这个它是怎么操作的?monitor又是个什么结构?以及在重量级锁的时候怎么通过monitor进行加锁的?这些问题我都没明白

老王:别急啊,小陈,这些东西我们下面都会来讲到的,现在我们先顺着思路来一点点讲解。

小陈:好啊,老王,那我们接下面学习什么?

老王:下一篇,我们顺着思路来讲讲《synchronized底层是怎么通过monitor进行加锁的? 》

小陈:好的,老王,我们下一章见面。

目录

JAVA并发专题 《筑基篇》

1.什么是CPU多级缓存模型?

2.什么是JAVA内存模型?

3.线程安全之可见性、有序性、原子性是什么?

4.什么是MESI缓存一致性协议?怎么解决并发的可见性问题?

JAVA并发专题《练气篇》

5.volatile怎么保证可见性?

6.什么是内存屏障?具有什么作用?

7.volatile怎么通过内存屏障保证可见性和有序性?

8.volatile为啥不能保证原子性?

9.synchronized是个啥东西?应该怎么使用?

10.synchronized底层之monitor、对象头、Mark Word?

11.synchronized底层是怎么通过monitor进行加锁的?

12.synchronized的锁重入、锁消除、锁升级原理?无锁、偏向锁、轻量级锁、自旋、重量级锁

13.synchronized怎么保证可见性、有序性、原子性?

JAVA并发专题《结丹篇》

  • JDK底层Unsafe类是个啥东西?
  • 15.unsafe类的CAS是怎么保证原子性的?

    16.Atomic原子类体系讲解

    17.AtomicInteger、AtomicBoolean的底层原理

    18.AtomicReference、AtomicStampReference底层原理

    19.Atomic中的LongAdder底层原理之分段锁机制

    20.Atmoic系列Strimped64分段锁底层实现源码剖析

    JAVA并发专题《金丹篇》

    21.AQS是个啥?为啥说它是JAVA并发工具基础框架?

    22.基于AQS的互斥锁底层源码深度剖析

    23.基于AQS的共享锁底层源码深度剖析

    24.ReentrantLock是怎么基于AQS实现独占锁的?

    25.ReentrantLock的Condition机制底层源码剖析

    26.CountDownLatch 门栓底层源码和实现机制深度剖析

    27.CyclicBarrier 栅栏底层源码和实现机制深度剖析

    28.Semaphore 信号量底层源码和实现机深度剖析

    29.ReentrantReadWriteLock 读写锁怎么表示?

  • ReentrantReadWriteLock 读写锁底层源码和机制深度剖析
  • JAVA并发专题《元神篇》并发数据结构篇

    31.CopyOnAarrayList 底层分析,怎么通过写时复制副本,提升并发性能?

    32.ConcurrentLinkedQueue 底层分析,CAS 无锁化操作提升并发性能?

    33.ConcurrentHashMap详解,底层怎么通过分段锁提升并发性能?

    34.LinkedBlockedQueue 阻塞队列怎么通过ReentrantLock和Condition实现?

    35.ArrayBlockedQueued 阻塞队列实现思路竟然和LinkedBlockedQueue一样?

    36.DelayQueue 底层源码剖析,延时队列怎么实现?

    37.SynchronousQueue底层原理解析

    JAVA并发专题《飞升篇》线程池底层深度剖析

  • 什么是线程池?看看JDK提供了哪些默认的线程池?底层竟然都是基于ThreadPoolExecutor的?
  • 39.ThreadPoolExecutor 构造函数有哪些参数?这些参数分别表示什么意思?

    40.内部有哪些变量,怎么表示线程池状态和线程数,看看道格.李大神是怎么设计的?

  • ThreadPoolExecutor execute执行流程?怎么进行任务提交的?addWorker方法干了啥?什么是workder?

  • ThreadPoolExecutor execute执行流程?何时将任务提交到阻塞队列? 阻塞队列满会发生什么?

  • ThreadPoolExecutor 中的Worker是如何执行提交到线程池的任务的?多余Worker怎么在超出空闲时间后被干掉的?

  • ThreadPoolExecutor shutdown、shutdownNow内部核心流程

  • 再回头看看为啥不推荐Executors提供几种线程池?

  • ThreadPoolExecutor线程池篇总结

  • 相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论