synchronized底层语义原理
Java虚拟机中的同步(Synchronization)基于进入和退出管程(Monitor)对象实现,无论是显示同步(有明确的monitorenter和monitorexit指令,即同步代码块)还是隐式同步都是如此。在Java语言中,同步用的最多到地方可能是被synchronized修饰的同步方法。同步方法并不是由monitorenter和monitorexit指令来实现同步到,而是由方法调用指令读取运行时常量池中方法到ACC_SYNCHRONIZED标志来隐式实现的,关于这点,稍后分析。下面先来了解一个概念:Java对象头,这对深入理解synchronized实现原理非常关键。
理解Java对象头与Monitor
在JVM中,对象在内存中到布局分为三块区域:对象头,实例数据和对齐填充。 如下:

- 实例变量: 存放类的属性数据信息,包括父类的属性信息,如果是数组的实例部分还包括数组的长度,这部分内存按4字节对齐。
- 填充数据:由于虚拟机要求对象起始地址必须是8字节的整数倍。填充数据不是必须存在的,仅仅是为了字节对齐。
而对于顶部,则是Java头对象,它是实现synchronized的锁对象的基础,这点我们重点分析它。 一般而言,synchronized使用的锁对象是存储在Java对象头里的,jvm采用2个字来存储对象头(如果对象是数组则会分配3个字,多出来到1个字记录的是数组长度),其主要结构是由Mark Word和Class Metadata Address组成,其结构说明如下:
| 虚拟机位数 |
头对象结构 |
说明 |
| 32/64bit |
Mark Word |
存储对象的hashcode, 锁信息或分代年龄或GC标志等信息 |
| 32/64bit |
Class Metadata Address |
类型指针指向对象的类元数据, JVM通过这个指针确定该对象是哪个类的实例 |
其中Mark Word在默认情况下存储着对象的HashCode, 分代年龄,锁标记等, 以下是32位JVM的Mark Word默认存储结构。
| 锁状态 |
25bit |
4bit |
1bit是否是偏向锁 |
2bit锁标志位 |
| 无锁状态 |
对象HashCode |
对象分代年龄 |
0 |
01 |
由于对象头的信息是与对象自身定义的数据没有关系到额外存储成本,因此考虑到JVM的空间效率,Mark Word被设计成为一个非固定的数据结构,以便存储更多有效的数据,它会根据对象本身的状态复用自己的存储空间,如32位JVM下,除了上述列出的Mark Word默认存储结构外,还有如下可能变化的结构:

其中,01且是否偏向锁为0就是无锁的状态,轻量级锁和偏向锁是Java 6对synchronized锁进行优化后新增加的,我们稍后简要分析。这里我们主要分析一下重量级锁也就是通常说的synchronized的对象锁,锁标识位10,其中指针指向的时monitor对象(也称为管程或监视器锁)的起始地址。每个对象都存在着一个monitor与之关联,对象与其monitor之间的关系有存在多种实现方式,如monitor可以与对象一起创建销毁或当线程试图获取对象锁时自动生成(Java对象在内存中被创建时(即当你使用new关键字创建一个对象实例时),它的监视器或内部锁就已经被隐式地初始化了。这个监视器或内部锁不是通过调用某个特定的方法来创建的,而是作为对象的一部分自动存在的。),但当一个monitor被某个线程持有后,它便处于锁定状态。在Java虚拟机(HotSpot)中,monitor是由ObjetMonitor实现的,其主要数据结构如下(位于HotSpot虚拟机源码ObjectMonitor.hpp文件,C++实现的)

ObjectMonitor() {
_header = NULL;
_count = 0; //记录个数
_waiters = 0,
_recursions = 0;
_object = NULL;
_owner = NULL;
_WaitSet = NULL; //处于wait状态的线程,会被加入到_WaitSet
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ;
FreeNext = NULL ;
_EntryList = NULL ; //处于等待锁block状态的线程,会被加入到该列表
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
}
ObjectMonitor中有两个队列, _WaitSet和_EntryList, 用来保存ObjectWaiter对象列表(每个等待锁的线程都会被封装成ObjectWaiter对象), _owner指向持有ObjectMonitor对象的线程。
当多个线程同时访问一段同步代码时,首先会进入_EntryList集合, 当线程获取到对象的monitor后,进入_owner区域, 并把monitor中到onwer变量设置为当前线程, 同时monitor中的计数器count+1。
若线程调用wait()方法,将释放当前持有的monitor, owner=null, count-1, 同时该线程进入waitSet集合中等待被唤醒。若当前线程执行完毕也将释放monitor(锁),并复位变量的值,以便其他线程进入获取monitor(锁)。 如下图所示:

最低0.47元/天 解锁文章
1873

被折叠的 条评论
为什么被折叠?



