Synchronized源码解读

一.java反编译工具

1.1 jdk自带的反编译工具

在target包中找到需要反编译的类右击打开控制台,然后在控制台中输入javap -c -v 类名.class

在这里插入图片描述

1.2使用ideal插件

1.2.1安装插

在这里插入图片描述

1.2.2 构建项目

在这里插入图片描述

1.2.3查看字节码文件

在这里插入图片描述
在这里插入图片描述

二.Synchronized源码查看

2.1 Synchronized如何加锁的

2.1.1java对象在内存中的布局

在这里插入图片描述
使用工具查看对象在内存中布局

		<dependency>
            <groupId>org.openjdk.jol</groupId>
            <artifactId>jol-core</artifactId>
            <version>0.9</version>
        </dependency>
ClassLayout.parseInstance(o).toPrintable()

在这里插入图片描述
markword说明
在这里插入图片描述

对象头Mark Word中的重量级锁指针指向的monitor对象,该对象是在HotSpot底层C++语言编写的(openjdk里面看),简单看一下代码:
//构造器
ObjectMonitor() {
_header = NULL;
_count = 0;
_waiters = 0,
_recursions = 0; // 递归;线程的重入次数,典型的System.out.println
_object = NULL; //  对应synchronized (object)对应里面的object
_owner = NULL; // 标识拥有该monitor的线程
_WaitSet = NULL; // 因为调用object.wait()方法而被阻塞的线程会被放在该队列中
_WaitSetLock = 0 ;
_Responsible = NULL;
_succ = NULL;
_cxq = NULL; // 竞争队列,所有请求锁的线程首先会被放在这个队列中
FreeNext = NULL;
_EntryList = NULL; // 阻塞;第二轮竞争锁仍然没有抢到的线程(偏向/可重入)
_SpinFreq = 0;
_SpinClock = 0;
OwnerIsThread = 0;
}

在这里插入图片描述
klass pointer说明

这一部分用于存储对象的类型指针,该指针指向它的类元数据,jvm通过这个指针确定对象是哪个类的实例。该指针的位长度为JVM的一个字大小,即32位的JVM为32位,64位的JVM为64位。
如果应用的对象过多,使用64位的指针将浪费大量内存,统计而言,64的JVM将会比32位的JVM多耗费50的内存。为了节约内存可以使用选项 -XX:+UseCompressedOops 开启指针压缩。其中 oop即ordinary object pointer 普通对象指针。

-XX:+UseCompressedOops 开启指针压缩
-XX:-UseCompressedOops  不开启指针压缩
对象头:Mark Word+Klass Pointer类型指针 未开启压缩的情况下
32位 Mark Word =4bytes ,类型指针 4bytes ,对象头=8bytes =64bits
64位 Mark Word =8bytes ,类型指针 8bytes ,对象头=16bytes=128bits;
注意:默认情况下,开启了指针压缩 可能只有12字节。

实例属性说明

类中的成员属性

对齐填充

由于HotSpot虚拟机的自动内存管理系统要求对象的起始地址必须是8字节的整数倍,也就是对象的大小必须是8字节的整数倍。

2.1.2 方法加锁和代码块加锁的区别

1.代码块中加ynchronized 通过字节码可以看到,是通过在指令前后加monitor
2.方法中加ynchronized 通过字节码可以看到,是通过在方法上加ACC_SYNCHRONIZED标识

在这里插入图片描述

2.1.2 为什么monitorexit会有两次

为了保证出现异常,锁也能被释放掉

2.1.3 monitorexit一定会有两次?

当我们同步代码块中出现异常后,只会有一次,并且异常会抛给monitor,monitor释放完锁后会然后再把异常返回给我们

2.1.4 JVM对这两条指令的描述

monitorenter :

Each object is associated with a monitor. A monitor is locked if and only if it has an owner. The thread that executes monitorenter attempts to gain ownership of the monitor associated with objectref, as follows: • If the entry count of the monitor associated with objectref is zero, the thread enters the monitor and sets its entry count to one. The thread is then the owner of the monitor. • If the thread already owns the monitor associated with objectref, it reenters the monitor, incrementing its entry count. • If another thread already owns the monitor associated with objectref, the thread blocks until the monitor's entry count is zero, then tries again to gain ownership

每个对象有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:
	如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。
	如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1。
	如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。
monitorexit: 

The thread that executes monitorexit must be the owner of the monitor associated with the instance referenced by objectref. The thread decrements the entry count of the monitor associated with objectref. If as a result the value of the entry count is zero, the thread exits the monitor and is no longer its owner. Other threads that are blocking to enter the monitor are allowed to attempt to do so.


执行monitorexit的线程必须是objectref所对应的monitor的所有者。指令执行时,monitor的进入数减1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去获取这个 monitor的所有权。 

通过这段话的描述,很清楚的看出Synchronized的实现原理,Synchronized底层通过一个monitor的对象来完成,wait/notify等方法其实也依赖于monitor对象,这就是为什么只有在同步的块或者方法中才能调用wait/notify等方法,否则会抛出java.lang.IllegalMonitorStateException的异常。

每个锁对象拥有一个锁计数器和一个指向持有该锁的线程的指针。

当执行monitorenter时,如果目标对象的计数器为零,那么说明它没有被其他线程所持有,Java虚拟机会将该锁对象的持有线程设置为当前线程,并且将其计数器加i。在目标锁对象的计数器不为零的情况下,如果锁对象的持有线程是当前线程,那么Java虚拟机可以将其计数器加1,否则需要等待,直至持有线程释放该锁。当执行monitorexit时,Java虚拟机则需将锁对象的计数器减1。计数器为零代表锁已被释放。

敖丙大佬的图

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值