synchronized相关知识

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问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值