synchronized同步锁实现原理(1)

本文深入探讨了Java中同步锁的实现机制,重点介绍了对象头的MarkWord结构及其在锁状态管理中的作用。通过分析MarkWord在不同锁状态下的存储信息,揭示了无锁、偏向锁、轻量级锁和重量级锁的转换过程。此外,还讨论了内存对齐填充的重要性以及其对CPU访问效率的影响。
摘要由CSDN通过智能技术生成

同步锁的实现猜想

  • 同步锁的核心特性是排他,要达到这个目的,多个线程必须去抢占同一个资源。
  • 在同一时刻只能有一个线程执行加了同步锁的代码,意味着同一时刻只允许一个线程抢占到这个共享资源,其余没抢占到的线程只能等待。
  • 处于等待状态的线程不能一直占用CPU资源,如果没抢占到锁就要被阻塞等待,并释放CPU资源。
  • 如果非常多的线程都被阻塞了,那么我们要通过一个容器来存储线程,当获得锁的线程执行任务并释放锁后,要从这个容器中唤醒一个线程,被唤醒的线程会再次尝试抢占锁。

 synchronized同步锁标记存储

实现锁互斥要满足的两个条件

  • 必须竞争同一个共享资源
  • 需要有一个标记来识别当前锁的状态是空闲还是繁忙

第一个条件通过lock对象来实现即可,第二个条件需要有一个地方来存储抢占锁的标记,否则当其他线程来抢占资源时,不知道当前是应该正常执行还是应该排队,实际上,这个锁标记是存储在对象头中的。

 Mark Word的存储结构

Java对象存储结构可以分为三个部分:对象头、实例数据、对齐填充。当我们构建一个Object lock = new Object()对象实例时,这个lock实例最终的存储结构就对应如下图所示的模型。

 对象头

Java中对象头由三个部分组成:Mark Word、Klass Pointer、Length。

  • Mark word

Mark Word 记录了与对象和锁的相关信息,当这个对象作为锁对象来实现synchronized的同步操作时,锁标记和相关信息都是存储在Mark Word中的,具体的相关存储结构如下图所示。

不管在32位还是64位系统中,Mark Word中都会包含GC分代年龄、锁状态标记、hashCode、epoch等信息。从图中可以看到一个锁阶段的字段,它包含五种状态分别是无锁、偏向锁、轻量级锁、重量级锁、GC标记。Mark Word使用2bit来存储这些锁状态,但是我们都知道2bit最多只能表达四种状态:01、00、10、11,那么第五种状态如何表达呢?Mark Word额外通过1bit来表达无锁和偏向锁,其中0表示无锁、1表示偏向锁。

  • Klass Pointer

Klass Pointer表示指向类指针,JVM通过这个指针来确定对象具体属于哪个类的实例。它的存储长度根据JVM的位数来决定,在32位的虚拟机中占4字节,在64位的虚拟机中占8字节,但是在JDK1.8中,由于默认开启了指针压缩,所以压缩后在64位系统中只占4字节。

  • length

表示数组的长度,只有构建对象数组时才会有数组长度属性。

实例数据

实例数据其实就是类中所有的成员变量,比如,一个对象中包含int、boolean、long、等成员变量,这些成员变量就存储在实例数据中。

实例数据占据的存储空间是由成员变量的类型决定的,比如boolean占1字节、int占4字节、long占8字节。如果成员变量是引用类型,那么它的数据大小与虚拟机位数和是否开启压缩指针有关系。

对齐填充

对齐填充本身没有任何含义,其目的是使得当前对象实例占用的存储空间是8字节的倍数,所以如果一个对象的字节大小不是8字节的整数倍,会使用对齐填充来达到这一目的。

为什么要通过增加存储空间来做填充呢?其实,这类的设计基本上都离不开空间换时间的理念。深层次的原因在于减少CPU访问内存的频率,从而达到性能提升的效果。

对象的实际存储

public class MarkWordExample{
    private int id;
    private String name;
    public static void main(String[] args){
        MarkWordExample example = new MarkWordExample();
    }
}

 对齐填充的作用

CPU在访问内存读取数据时,并不是按照逐个字节来访问的,而是以字长(Word Size)为单位来访问的。字长是指CPU一次能够并行处理的二进制位数,字长总是8字节的整数倍。

在64位操作系统中,CPU访问内存读取数据的单位就是8字节,在32位的操作系统中,CPU访问内存读取数据的单位就是4字节,这样设计的目的就是减少CPU访问内存的次数,提升CPU的使用率。

假设一个变量在内存中的存储跨越两个字长,如下图所示结构,比如一个int类型的变量y占4个字节,左边为未对齐填充的内存布局,他会存储在跨字长存储,右边表示对齐填充后的内存布局,不存在跨字长存储。

在未对齐填充的内存布局中,CPU要读取变量y,由于跨越了两个字长,所以需要访问两次内存,第一次读取第一个字长获得最后三个有效字节,第二次读取第二个字长获得第二个字长的第一个有效字节,然后在寄存器中进行拼接。 

但是在对齐填充的内存布局中,CPU读取变量x或者与,都只需要一次内存访问,虽然做了无效填充,但是访问内存的次数减少了,这种方式的计算性能更高,因此本质上来说就是一种空间换时间的方式。

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值