Synchronized重量级锁加锁解锁执行逻辑
2025-02-26 15:29:06 0 举报
Synchronized重量级锁加锁解锁执行逻辑,分享给大家学习。 更多干货内容,欢迎关注我的公众号:Fox爱分享
作者其他创作
大纲/内容
if (TryLock (Self) > 0)
if (QMode == 4 && _cxq != NULL)
通过CAS尝试获取锁,就是将monitor对象的_owner指针指向当前线程
将_owner属性置为NULL,释放锁
if (QMode == 1)
false
if (_recursions != 0)
for (;;){}
ObjectMonitor::TrySpin_VaryDuration
判断是否是当前线程获取锁
如果_owner不是当前线程
再次尝试获取锁
线程不甘心直接被挂起,挂起之前多次尝试获取锁
OrderAccess::storeload() ;
获取锁成功,跳出循环
_EntryList不为空,直接从_EntryList中唤醒线程
EntryList
进入到循环,获取锁失败调用park挂起当前线程,获取锁成功跳出循环
Trigger->unpark() ;
自适应自旋,尝试获取锁
被唤醒的线程有可能是cxq或者EntryList队首元素对应的线程
if ((Knob_SpinAfterFutile & 1) && TrySpin (Self) > 0) break ;
将当前线程的node从cxq或EntryList中移除
尝试获取锁
重量级锁加锁
true
终止自旋场景:1. 成功获取锁2. 达到自旋次数3.自旋的过程中会判断是否进入安全点,如果进入则终止自旋(每自旋256次就会检查一次)
如果重入计数器不为0.减1后返回
objectMonitor.cpp#ObjectMonitor::enter
w = _cxq ;if (w == NULL) continue
_owner = THREAD ; _recursions = 0 ; OwnerIsThread = 1 ;
Self->_ParkEvent->park ((jlong) RecheckInterval);
否
_cxq不为空,_EntryList为空的情况
CAS获取锁成功
挂起之前再次尝试获取锁,如果成功跳出循环
OrderAccess::fence()
获取锁失败,再次使用自旋,尝试获取锁
_owner是当前线程
设计的精髓,并发场景入队操作CAS的应用
objectMonitor.cpp#ObjectMonitor::exit
最后调用内存屏障保证可见性
if (QMode == 3 && _cxq != NULL)
Self->_ParkEvent->park()
ObjectWaiter node(Self);node.TState = ObjectWaiter::TS_CXQ ;
获取锁
for(;;){ ObjectMonitor::EnterI}
QMode == 4 并且 cxq不为空:把_cxq队首元素放入_EntryList的头部
这就是传说中的自适应自旋逻辑
ObjectMonitor::TryLock
将node节点插入到_cxq队列的头部,期间也会尝试重新获取锁
线程唤醒并成功获取锁之后,会从cxq或EntryList队列中移除对应node
注意:被唤醒的线程不一定在cxq队列中了,有可能在EntryList中
CAS成功获取锁
TrySpin
if (Self->is_lock_owned ((address)cur))
失败,调用 park 函数挂起当前线程
竞争失败的线程会被挂起,等待唤醒主要逻辑:1. 将当前线程封装为 node 塞到cxq队列 的头部2. 调用 park 挂起当前线程3. 被唤醒后再次尝试获取锁(在唤醒时候会根据不同的唤醒策略定义 cxq 与 EntryList 的优先级)
CAS失败
线程被唤醒的第一时间就会尝试获取锁,如何成功跳出循环
循环调用此方法两种结果:1. 获取锁成功2. 竞争失败,线程会被挂起,等待唤醒
if (QMode == 2 && _cxq != NULL)
OrderAccess::fence() ;
如果owner位于当前线程调用栈帧,说明该锁是轻量级锁膨胀来的
if (TryLock (Self) > 0) break
是,重入计数置为1,设置owner字段为当前线程(之前owner是指向Lock Record的指针)
if (TrySpin (Self) > 0)
会发生系统调用,涉及到用户态和内核态之间的切换,linux系统会调用pthread_cond_signal唤醒挂起的线程
修改owner属性
在获取锁时,是将当前线程插入到cxq的头部,而释放锁时,默认策略是:如果EntryList为空,则将cxq中的元素按原有顺序插入到到EntryList,并唤醒第一个线程,,也就是当EntryList为空时,是后来的线程先获取锁。_EntryList不为空,直接从_EntryList中唤醒线程。
CAS失败,继续自旋
if (cur == Self)
其他线程占用该锁,直接返回
设计精髓: 非公平锁的优化如果某个线程正在自旋抢占该锁,则会抢占成功,这种策略会优先保证通过自旋抢占锁的线程获取锁,而其他处于等待队列中的线程则靠后
根据QMode的不同,会执行不同的唤醒策略:1.QMode = 2且cxq非空:取cxq队列队首的ObjectWaiter对象,调用ExitEpilog方法,该方法会唤醒ObjectWaiter对象的线程,然后立即返回;2.QMode = 3且cxq非空:把cxq队列插入到EntryList的尾部;3.QMode = 4且cxq非空:把cxq队列插入到EntryList的头部;4. 只有QMode=2的时候会提前返回,等于0、3、4的时候都会继续往下执行:a. 如果EntryList的首元素非空,就取出来调用ExitEpilog方法,该方法会唤醒ObjectWaiter对象的线程,然后立即返回;b. 如果EntryList的首元素为空,就将cxq的所有元素放入到EntryList中(如果Qmode=1会反转顺序),然后再从EntryList中取出来队首元素执行ExitEpilog方法,然后立即返回;
如果当前是无锁状态、锁重入、当前线程是之前持有轻量级锁的线程则进行简单操作后返回。
CAS成功直接返回
根据QMode的不同会有不同的唤醒策略,默认为0
if (THREAD != _owner)
通过unpark唤醒cxq或EntryList队首元素中对应的线程
if (Knob_SpinEarly && TrySpin (Self) > 0)
将当前线程封装到node节点中,类型是ObjectWaiter,设置状态为TS_CXQ
自旋过程中会通过CAS尝试获取锁
tail
w = _EntryList ; if (w != NULL)
唤醒目标线程
自旋结束,获取锁失败
此处是重量级锁最大的开销,系统调用会涉及到用户态和内核态的切换。在linux系统中会调用pthread_cond_wait或pthread_cond_timewait挂起线程。
多线程资源竞争场景设计的精髓: 序列化访问共享资源,获取锁失败线程等待挂起,被唤醒后继续尝试获取锁,直到获取锁跳出循环
QMode == 3 并且 cxq不为空:把_cxq队首元素放入_EntryList的尾部
通过自旋尝试获取锁
获取锁失败,准备在_cxq上排队
加入一个写屏障,让修改立即生效
是,表示是重入锁,可重入次数加1,返回
此时,当前线程不甘心被挂起
重量级锁解锁
if (THREAD->is_lock_owned((address) _owner))
终止自旋
当前线程是否是之前持有轻量级锁的线程。由轻量级锁膨胀且第一次调用enter方法,那cur是指向Lock Record的指针
_cxq不为空,_EntryList为空,QMode==1: 将_cxq中的元素转移到_EntryList,并反转顺序
将_cxq中的元素转移到_EntryList
当前线程被唤醒
如果_cxq和_EntryList都为空,循环
QMode == 2并且cxq不为空 : 直接取_cxq队首元素,调用ExitEpilog唤醒对应的线程
head
ObjectMonitor::ExitEpilog
当前线程是之前持有轻量级锁的线程。
调用系统同步操作之前,先尝试自旋获取锁,这样做的目的是为了减少执行操作系统同步操作带来的开销

收藏

收藏
0 条评论
下一页