monitor原理及执行步骤
因为每个对象头中都存在monitor锁对象,所以java中可以用对象加锁。
对象结构大概分为对象头、实例变量和填充字节。
- 对象头
- Mark Word(标记字段):存储对象的hashCode、锁信息或分代年龄或GC标志等信息
- Klass Pointer(类型指针):对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例
- 数组长度:只有数组对象保存了这部分数据。该数据在32位和64位JVM中长度都是32bit。
- 实例变量:实例数据部分是对象真正存储的有效信息,也是在程序代码中所定义的各种类型的字段内容。无论是从父类继承下来的,还是在子类中定义的,都需要记录起来。
- 填充字节:第三部分对齐填充并不是必然存在的,也没有特别的含义,它仅仅起着占位符的作用。由于HotSpot VM的自动内存管理系统要求对象起始地址必须是8字节的整数倍,换句话说,就是对象的大小必须是8字节的整数倍。而对象头部分正好是8字节的倍数(1倍或者2倍),因此,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。
每一个锁都对应一个monitor对象,在HotSpot虚拟机中它是由ObjectMonitor实现的(C++实现)。每个对象都存在着一个monitor与之关联,对象与其monitor之间的关系有存在多种实现方式,查看部分ObjectMonitor.cpp源码:
ObjectMonitor () {
// 指向持有ObjectMonitor对象的线程地址。
_owner = NULL;
// 存放调用wait方法,而进入等待状态的线程的队列。
_WaitSet = NULL
// 这里是等待锁block状态的线程的队列。
_EntryList = NULL;
// 锁的重入次数。
_recursions = 0;
// 线程获取锁的次数。
_count = 0;
}
执行步骤:
- 当多个线程同时访问时,这些线程先被放进**_EntryList队列**中,线程处于blocked状态。
- 当其中的一个线程获取到对象的monitor后,该线程进入running状态,_owner指向当前线程,_count加1表示锁被一个线程获取到。
- 当running状态线程调用wait()方法,释放当前线程的monitor对,并进入waiting状态。_owner变为NULL,_count减1,将线程加入都_WaitSet队列中,当有线程notify()该线程时,将该线程加入到_EntryList队列中参与锁的竞争。
- 当线程执行完成时,释放monitor对象,_owner为NULL,_count减1。
锁的四种状态(无锁状态、偏向锁、轻量级锁、重量级锁)
JDK6之前只有两个状态:无锁、有锁(重量级锁),而在JDK6之后对synchronized进行了优化,新增了两种状态,总共就是四个状态:无锁状态、偏向锁、轻量级锁、重量级锁。