1、对象头Markword
2、锁升级过程
无锁
偏向锁:只有一个线程过来加锁,Markword对应变化:偏向线程ID存储当前线程ID,偏向锁标志位置成1,锁标志位置为01;此后如果当前线程再次获取锁,只需对比偏向线程ID即可:
轻量级锁:当第二个线程过来获取锁,并且获取成功,则升级为轻量级锁,对应Markword变化如下:在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,拷贝对象头的Markword到栈帧的Lock Record,然后将对象头的Markword更新为指向Lock Record的指针,并将Lock Record的owner指针指向对象头的Markword。
重量级锁:轻量级锁获取失败,则升级为重量级锁。底层是通过ObjectMonitor实现的。
3、重量级锁的实现原理
ObjectMonitor定义:
// initialize the monitor, exception the semaphore, all other fields
// are simple integers or pointers
ObjectMonitor() {
_header = NULL;
_count = 0;
_waiters = 0,
_recursions = 0;
_object = NULL;
_owner = NULL;
_WaitSet = NULL;
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ;
FreeNext = NULL ;
_EntryList = NULL ;
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
_previous_owner_tid = 0;
}
ObjectMonitor主要有三个队列和一个变量比较重要:
三个队列:_WaitSet:等待池。调用wait()方法的线程会进入此队列,等待线程组成一个双向循环链表:
_cxq:相当于栈结构,先进后出。当执行monitorenter指令获取锁失败,或者调用notify()或者notifyAll()时,根据默认策略(默认policy=2),会把当前线程放入_cxq栈顶(相当于头插)。节点被push 到_cxq 列表之后,还会通过自旋尝试获取锁,如果还是没有获取到锁则通过park将当前线程挂起等待被唤醒。
_EntryList:存放进入或者重新进入时被阻塞的线程,也就是竞争锁失败的线程。当前持有锁的线程执行完毕,唤醒下一个线程时,会根据QMode策略(默认QMode=0)唤醒:如果_EntryList不为空,则从_EntryList中取出线程,执行unpark;如果为空,则将_cxq中的元素按原有顺序插入到_EntryList中,并唤醒第一个线程。也就是当_EntryList为空时,是后来的线程先获取锁;_EntryList不为空,直接从_EntryList中唤醒线程。
执行过程如下:
问:为什么锁池要有两个队列_cxq和_EntryList?
答:1、主要是考虑到性能问题。当前持有锁的线程执行完毕唤醒下一个线程时,会先判断_EntryList是否为空,如果不为空就唤醒头节点,如果为空就唤醒_cxq栈顶结点。而notify()、notiryAll()也会操作_cxq,这样冲突的概率就大大增加,导致性能降低。2、为了防止ABA问题:_cxq栈的操作包含push入栈和pop出栈,这样就会产生ABA问题,把两者分开,_cxq就只有入栈push操作,出栈就只能在持有锁的线程唤醒下一个等待节点时从_EntryList取出,不会出现ABA问题。