深入理解并发编程之synchronized的monitor与对象布局原理

深入理解并发编程之synchronized的monitor与对象布局原理


一、Synchronized回顾

Synchronized是Java的关键字,是重量级锁,可以用在方法和代码块上。如果在普通方法上加上Synchronized锁,则使用this锁; 在静态同步方法上,则使用当前类的class字节码;也可以自定义锁的对象。
Synchronized底层是使用C++ 写的。

二、Synchronized关键字详解

1.以汇编的角度分析Synchronized

先看下面一段代码:

public class Test005 extends Thread {
    private Object lockObject = new Object();

    @Override
    public void run() {
        a();
    }

    public void a() {
        //  虚拟机里面
        synchronized (lockObject) {
            System.out.println("我是A调用B");
            // 当前线程变为阻塞状态同时 释放锁
            lockObject.notify();
            b();
        }
    }

    private void b() {
        synchronized (lockObject) {
            System.out.println("我是B");
        }
    }

    public static void main(String[] args) {

    }

}

先把上面代码编译为class文件,然后使用javap反汇编为汇编代码

javap -p -v .\Test005.class

下面是反汇编出来的部分代码a()方法,b()方法的反汇编跟a差不多就不看了
在这里插入图片描述

可以看到当我们加了synchronized关键字后,汇编出来的代码多了两个操作monitorentermonitorexit。由此可以得出这就是synchronized关键字实现的核心。

monitorenter详解

下面看一下Java的官方介绍:

https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.monitorenter
在这里插入图片描述

翻译过来就是这样的:
每一个对象都会和一个监视器monitor关联。监视器被占用时会被锁住,其他线程无法来获取该monitor。 当JVM执行某个线程的某个方法内部的monitorenter时,它会尝试去获取当前对象对应的monitor的所有权。其过程如下:

  1. 若monior的进入数为0,线程可以进入monitor,并将monitor的进入数置为1。当前线程成为monitor的owner(所有者)
  2. 若线程已拥有monitor的所有权,允许它重入monitor,则进入monitor的进入数加1
  3. 若其他线程已经占有monitor的所有权,那么当前尝试获取monitor的所有权的线程会被阻塞,直到monitor的进入数变为0,才能重新尝试获取monitor的所有权。

monitorexit详解

具体官方链接不上了,就在上个链接的下面

在这里插入图片描述

翻译过来就是这样的:

  1. 能执行monitorexit指令的线程一定是拥有当前对象的monitor的所有权的线程。
  2. 执行monitorexit时会将monitor的进入数减1。当monitor的进入数减为0时,当前线程退出monitor,不再拥有monitor的所有权,此时其他被这个monitor阻塞的线程可以尝试去获取。

回头看上面的汇编截图,是不是看到两个monitorexit指令,这是为啥呢?
其实是这样的第一个monitorexit是正常退出,而第二个monitorexit代表的是异常退出,可以看到第二个下面紧跟着throw以及一个异常表。

monitor简介

monitor才是真正的锁,是一个c++对象拥有两个重要的属性:

  • owner:拥有这把锁的线程,也就是当前锁标记持有者。
  • recursions:记录线程拥有锁的次数,也就是重入锁重入的次数。按照上面的demo可以这样理解每次执行monitorenter都会+1,a方法调用了b方法,这是一个重入锁,当进入a方法的时候recursions由0变为1,当由a再进入b的时候recursions由1变成了2;而每次执行monitorexit都会-1,由b退出的时候recursions变成了1,由a退出的时候recursions变成了0,这个时候代表这个重入锁完全退出了,可以正常释放锁标记被其他线程去争夺了。

2.以c++的角度分析monitor

monitor属性分析

monitor是由c++写的,我们需要使用虚拟机来查看其源码,下面是虚拟机下载地址:

http://hg.openjdk.java.net/jdk8

下载下来之后在Windows文件管理搜索objectmonitor关键字就会找到:
在这里插入图片描述
打开这个文件看一下monitor有下面属性:
在这里插入图片描述
上面解释了synchronized是重入锁的原因,下面根据属性说一下synchronized为非公平锁的原因,_cxq是当没有线程持有锁标记的时候形成的单项链表排队,看着是公平,但是这里每次释放锁后都重新生成,也就是说每次释放锁都会重新排队排第一位的持有锁标记(每次释放锁_EntryList里面所有阻塞的线程重新生成_cxq)。

三、java的对象布局

1.java对象布局

在JVM中,对象在内存中的布局分为三个部分:对象头、实例数据和对齐填充。
在这里插入图片描述
对象头包含Mark Word与Klass Pointer

Mark Word:用于存储对象自身的运行时数据, 如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。
Klass Pointer:对象指向它的类的元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。(数组,对象头中还必须有一块用于记录数组长度的数据,因为虚拟机可以通过普通Java对象的元数据信息确定Java对象的大小,但是从数组的元数据中无法确定数组的大小。 )

64位对象头Mark Word占位图:
在这里插入图片描述
上虚拟机源码(只看64位的):
在这里插入图片描述

详细代码查看对象占用内存的大小,需要引入jol-core:

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

引入后看下面代码:

public class MeiteLock {
    private int userId1; // 4个字节
    
    public static void main(String[] args) {
        MeiteLock meiteLock = new MeiteLock();
          System.out.println(meiteLock.hashCode());
          System.out.println(Integer.toHexString(meiteLock.hashCode())); //16进制hashcode
          System.out.println(ClassLayout.parseInstance(meiteLock).toPrintable()); //以表格的形式打印
    }
}

可以看到打印结果:
在这里插入图片描述

对象头占了16个字节,实例数据int的uid占了4个字节,还有4个字节的自动填充数据(必须是8的倍数,不足的需要填充,仅仅是占位符),总共24个字节。黄色的是对象布局里面的hashcode,注意倒着看的,参照Mark Word布局图:第一个字节01是锁标志位+偏向锁+分代年龄+cms_free,然后是hashcode,再就是unused。
针对自动填充数据下面我们再加个int属性uid2,这时候正好满足8的倍数,就不需要填充了。
在这里插入图片描述

这里需要注意的是虚拟机会开启自动指针压缩,需要去掉自动指针压缩

-XX:-UseCompressedOops

2.基本数据类型占用字节

  • bit --位:位是计算机中存储数据的最小单位,指二进制数中的一个位数,其值为“0”或“1”。
  • byte --字节:字节是计算机存储容量的基本单位,一个字节由8位二进制数组成。在计算机内部,一个字节可以表示一个数据,也可以表示一个英文字母,两个字节可以表示一个汉字。

1Byte=8bit (1B=8bit)
1KB=1024Byte(字节)=8*1024bit
1MB=1024KB
1GB=1024MB
1TB=1024GB

类型字节
int4 byte
short2 byte
long8 byte
byte1 byte
char2 byte
float4 byte
double8 byte
boolean1 byte

内容来源: 蚂蚁课堂

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值