1.synchronized如何实现同步的?
2.synchronized对象锁保存在哪里?
3.如何加锁和释放锁,1.6做了哪些优化?
4.如何将线程挂起等待和唤醒?
我们之前了解过synchronized同步普通方法和静态方法,以及同步代码块,其中的区别在于synchronized具体锁的是那个对象,是具体的实例还是类对象,即class 我们通过反编译:javap -verbose SynchronizedTest.class >sync.txt。可以看出使用synchronized之后的一些细微的变化,方法上的同步关键字,class文件反编译后会发现flags中有ACC_SYNCHRONIZED标识。
1.方法级的同步是隐式的:同步方法的常量池中会有一个ACC_SYNCHRONIZED标志。当某个线程要访问某个方法的时候,会检查是否有ACC_SYNCHRONIZED,如果有设置,则需要先获得对象的内置锁,然后开始执行方法,方法执行之后再释放锁。
2.同步代码块:依赖monitorenter和monitorexit相关标识,通过JVM内置锁对象来实现加锁和释放锁。
对象头信息简介:首先每一个对象在堆内存中,实例对象会有对象头信息,对象实体信息,还有填充信息。其中头信息中包括锁状态,GC年纪,Hash值和监控monitor对象的引用等信息,所以锁相关的记录都会保存在该对象中,对象头信息MarkOOP对象中持有该对象的引用指针。具体可以参考下面的图,图来源网络。
synchronzied实现同步用到了对象的内置锁(ObjectMonitor),锁相关的计数器和线程竞争情况都会依赖monitor对象。下面看一下JVM中改对象头中的监控对象的具体实现,该对象是在HotSpot底层C++语言编写的。具体可以在github上查看具体的源码:
[https://github.com/openjdk-mirror/jdk7u-hotspot/blob/master/src/share/vm/runtime/objectMonitor.hpp]中查看hotspot-JVM具体源码的实现,其中objectMonitor.hpp中是该对象的结构的一些定义信息,包括对象属性和方法定义等,objectMonitor.cpp是具体的实现。首先我们看一下hpp文件中objectMonitor监控对象的结构。
3.objectMonitor.hpp中ObjectMonitor的结构:
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 ;//_owner从该双向循环链表中唤醒线程结点
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
}
4.objectMonitor.hpp中相关方法:看到下面这些方法大家可能并不陌生,每个对象都会有相应Object中的方法,其中wait和notify,就是超类中的方法,其实底层实现就在这里。
bool try_enter (TRAPS) ;//尝试获取锁,返回Boolean
void enter(TRAPS);//获取锁,jvm触发monitorenter指令的时候,底层将调用该方法
void exit(TRAPS);//释放锁,jvm触发monitorexit指令的时候调用
void wait(jlong millis, bool interruptable, TRAPS);//获得对象锁,调用对象的wait()方法,将挂起线程
void notify(TRAPS);//调用对象的notify()方法,唤醒其中的一个等待线程
void notifyAll(TRAPS);//调用对象的notifyAll()方法,将唤醒所有的等待线程
JDK1.6版本之前,同步关键字采用的是重锁,所以先讲一下重锁的获取和释放,后面再谈1.6以后的锁升级优化。ObjectMonitor中维护了一个_cxq队列和_EntryList队列,_owner和_WaitSet等待唤醒线程队列。
1._cxq队列多线程竞争锁进入时的单向链表;
2._EntryList是_owner待唤醒的所有竞争线程对象
3._owner对象:是当前获得锁的线程,当调用exit方法后,也就是出同步方法或者代码块之后,monitor对象中锁状态和等待队列会发生变化,并且通知其他线程又可以重新进行锁竞争。
4.调用wait方法, 将会将该线程封装成等待节点信息,并存放到_WaitSet中等待,并且将线程park挂起,释放对象锁,此时owner对象会转到_WaitSet中,
5.当调用notify()唤醒操作: _WaitSet等待的线程又会重新的请求锁资源。具体的可以看一下源码如何将线程打包成节点,存入_WaitSet链表中,出队的时候调用DequeueWaiter方法移出_waiterSet第一个结点,会触发unpark唤醒队列第一个节点中的线程。
可以参考下图进行理解,图来源网络。
1.enter(TRAPS)是重量级锁的获锁过程,属于1.6之前的做法,1.6 jvm进行锁升级相关的优化,下一篇会针对jvm-hotspot锁升级源码来分析锁升级的过程。
2.Atomic::cmpxchg_ptr:Atomic::cmpxchg_ptr hotspot源码文件在Atomic.hpp中static intptr_t cmpxchg_ptr(intptr_t exchange_value, volatile intptr_t* dest, intptr_t compare_value),该方法就是重中之重的CAS原子比较替换,它是jvm原子操作的基础,我们看一下hotspot中该方法的注释;
3.cmpxchg_ptr源码的注解翻译:(1).执行* dest和compare_value的原子比较,并用exchange_value交换* dest; (2).如果比较成功,返回* dest的先前值,保证双向记忆;
void ATTR ObjectMonitor::enter(TRAPS) {
// The following code is ordered to check the most common cases first
// and to reduce RTS->RTO cache line upgrades on SPARC and IA32 processors.
//首先检查最常见的情况 并减少SPARC和IA32处理器上的RTS-> RTO缓存线升级。
//self 为当前线程
Thread * const Self = THREAD ;
void * cur ;
//调用cmpxchg函数进行cas原子替换_owner为当前线程
cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL) ;
//如果替换成功之后,cur之前的值为null,表示获得锁成功
if (cur == NULL) {
// Either ASSERT _recursions == 0 or explicitly set _recursions = 0.
assert (_recursions == 0 , "invariant") ;
assert (_owner == Self, "invariant") ;
// CONSIDER: set or assert OwnerIsThread == 1
return ;
}
//如果cas比较替换之后,如果之前的_owner指向该THREAD,那么该线程是重入,_recursions++
if (cur == Self) {
// TODO-FIXME: check for integer overflow! BUGID 6557169.
_recursions ++ ;//重入锁次数
return ;
}
//如果当前线程是第一次进入该monitor,设置_recursions为1,并且把当前线程赋值给_owner,标识当前线程已经获得锁
if (Self->is_lock_owned ((address)cur)) {
assert (_recursions == 0, "internal state error");
_recursions = 1 ;
// Commute owner from a thread-specific on-stack BasicLockObject address to
// a full-fledged "Thread *".
_owner = Self ;
OwnerIsThread = 1 ;
return ;
}
// We've encountered genuine contention.
assert (Self->_Stalled == 0, "invariant") ;
Self->_Stalled = intptr_t(this) ;
// Try one round of spinning *before* enqueueing Self
// and before going through the awkward and expensive state
// transitions. The following spin is strictly optional ...
// Note that if we acquire the monitor from an initial spin
// we forgo posting JVMTI events and firing DTRACE probes.
//尝试一轮旋转*之前*排队Self并在经历尴尬和昂贵的状态转换之前。
//以下旋转是严格可选的...请注意,如果我们从初始旋转中获取监视器,我们将放弃发布JVMTI事件并触发DTRACE探测器。
if (Knob_SpinEarly && TrySpin (Self) > 0) {
assert (_owner == Self , "invariant") ;
assert (_recursions == 0 , "invariant") ;
assert (((oop)(object()))->mark() == markOopDesc::encode(this), "invariant") ;
Self->_Stalled = 0 ;
return ;
}
// Prevent deflation at STW-time. See deflate_idle_monitors() and is_busy().
// Ensure the object-monitor relationship remains stable while there's contention.
//获得锁,则原子增加_count值,表示锁的计数器
Atomic::inc_ptr(&_count);
{ // Change java thread status to indicate blocked on monitor enter.
//更改java线程状态,并且阻止其他线程进入监视器。
JavaThreadBlockedOnMonitorEnterState jtbmes(jt, this);
DTRACE_MONITOR_PROBE(contended__enter, this, object(), jt);
if (JvmtiExport::should_post_monitor_contended_enter()) {
JvmtiExport::post_monitor_contended_enter(jt, this);
}
OSThreadContendState osts(Self->osthread());
ThreadBlockInVM tbivm(jt);
Self->set_current_pending_monitor(this);
//自旋执行ObjectMonitor::EnterI方法等待锁的释放
for (;;) {
jt->set_suspend_equivalent();
// cleared by handle_special_suspend_equivalent_condition()
// or java_suspend_self()
//没有获得锁的线程,放入_EntryList中
EnterI (THREAD) ;
if (!ExitSuspendEquivalent(jt)) break ;
// We have acquired the contended monitor, but while we were
// waiting another thread suspended us. We don't want to enter
// the monitor while suspended because that would surprise the
// thread that suspended us.
//已经获得锁的线程,如果被其他线程暂停,当前线程就需要退出监视器
_recursions = 0 ;
_succ = NULL ;
exit (Self) ;
jt->java_suspend_self();
}
Self->set_current_pending_monitor(NULL);
}
//锁如果释放,则原子递减_count值
Atomic::dec_ptr(&_count);
assert (_count >= 0, "invariant") ;
Self->_Stalled = 0 ;
DTRACE_MONITOR_PROBE(contended__entered, this, object(), jt);
if (JvmtiExport::should_post_monitor_contended_entered()) {
JvmtiExport::post_monitor_contended_entered(jt, this);
}
if (ObjectMonitor::_sync_ContendedLockAttempts != NULL) {
ObjectMonitor::_sync_ContendedLockAttempts->inc() ;
}
}
重锁获取的流程:
1.cmpxchg_ptr替换当前线程,判断是内置锁中的_owner是否替换成成功,cmpxchg_ptr函数返回为空则代表替换成功。
2.如果失败,则需要调用EnterI函数将当前线程封装到等待队列,并将线程挂起等待被唤醒。
重锁的释放,需要做哪些事情?也就是当方法执行同步代码块之后,退出锁的逻辑,下面是具体的释放锁的过程。就是唤醒正在等待竞争的线程,也就是需要唤醒内置锁ObjectMonitor中的_EntryList 中的线程,重新获取锁,并且执行同步代码。
void ATTR ObjectMonitor::exit(TRAPS) {
Thread * Self = THREAD ;
if (THREAD != _owner) {
if (THREAD->is_lock_owned((address) _owner)) {
assert (_recursions == 0, "invariant") ;
_owner = THREAD ;
_recursions = 0 ;
OwnerIsThread = 1 ;
} else {
TEVENT (Exit - Throw IMSX) ;
assert(false, "Non-balanced monitor enter/exit!");
if (false) {
THROW(vmSymbols::java_lang_IllegalMonitorStateException());
}
return;
}
}
if (_recursions != 0) {
_recursions--; // _recursions重入的次数递减
TEVENT (Inflated exit - recursive) ;
return ;
}
if ((SyncFlags & 4) == 0) {
_Responsible = NULL ;
}
for (;;) {
assert (THREAD == _owner, "invariant") ;
if (Knob_ExitPolicy == 0) {
OrderAccess::release_store_ptr (&_owner, NULL) ; // drop the lock
OrderAccess::storeload() ; // See if we need to wake a successor
if ((intptr_t(_EntryList)|intptr_t(_cxq)) == 0 || _succ != NULL) {
TEVENT (Inflated exit - simple egress) ;
return ;
}
TEVENT (Inflated exit - complex egress) ;
if (Atomic::cmpxchg_ptr (THREAD, &_owner, NULL) != NULL) {
return ;
}
TEVENT (Exit - Reacquired) ;
} else {
if ((intptr_t(_EntryList)|intptr_t(_cxq)) == 0 || _succ != NULL) {
OrderAccess::release_store_ptr (&_owner, NULL) ; // drop the lock
OrderAccess::storeload() ;
// Ratify the previously observed values.
if (_cxq == NULL || _succ != NULL) {
TEVENT (Inflated exit - simple egress) ;
return ;
}
if (Atomic::cmpxchg_ptr (THREAD, &_owner, NULL) != NULL) {
TEVENT (Inflated exit - reacquired succeeded) ;
return ;
}
TEVENT (Inflated exit - reacquired failed) ;
} else {
TEVENT (Inflated exit - complex egress) ;
}
}
guarantee (_owner == THREAD, "invariant") ;
ObjectWaiter * w = NULL ;
int QMode = Knob_QMode ;
//QMode == 2:从_cxq队列中取出线程,并且调用unpark唤醒
if (QMode == 2 && _cxq != NULL) {
w = _cxq ;
assert (w != NULL, "invariant") ;
assert (w->TState == ObjectWaiter::TS_CXQ, "Invariant") ;
ExitEpilog (Self, w) ;//这里会唤醒,unpark _EntryList中等待的线程
return ;
}
//QMode == 3:将_cxq队列尾插入_EntryList中
if (QMode == 3 && _cxq != NULL) {
w = _cxq ;
//省去一些代码
ObjectWaiter * Tail ;
for (Tail = _EntryList ; Tail != NULL && Tail->_next != NULL ; Tail = Tail->_next) ;
if (Tail == NULL) {
_EntryList = w ;
} else {
Tail->_next = w ;
w->_prev = Tail ;
}
}
//QMode == 4:将_cxq队列头插入_EntryList中
if (QMode == 4 && _cxq != NULL) {
w = _cxq ;
if (_EntryList != NULL) {
q->_next = _EntryList ;
_EntryList->_prev = q ;
}
_EntryList = w ;
}
w = _EntryList ;
if (w != NULL) {
assert (w->TState == ObjectWaiter::TS_ENTER, "invariant") ;
ExitEpilog (Self, w) ;//这里会唤醒,unpark _EntryList中等待的线程
return ;
}
w = _cxq ;
if (w == NULL) continue ;
// Drain _cxq into EntryList - bulk transfer.
// First, detach _cxq.
// The following loop is tantamount to: w = swap (&cxq, NULL)
for (;;) {
assert (w != NULL, "Invariant") ;
ObjectWaiter * u = (ObjectWaiter *) Atomic::cmpxchg_ptr (NULL, &_cxq, w) ;
if (u == w) break ;
w = u ;
}
TEVENT (Inflated exit - drain cxq into EntryList) ;
//QMode == 1? _EntryList为倒置后的cxq队列
if (QMode == 1) {
// QMode == 1 : drain cxq to EntryList, reversing order
// We also reverse the order of the list.
ObjectWaiter * s = NULL ;
ObjectWaiter * t = w ;
ObjectWaiter * u = NULL ;
while (t != NULL) {
guarantee (t->TState == ObjectWaiter::TS_CXQ, "invariant") ;
t->TState = ObjectWaiter::TS_ENTER ;
u = t->_next ;
t->_prev = u ;
t->_next = s ;
s = t;
t = u ;
}
_EntryList = s ;
assert (s != NULL, "invariant") ;
} else {
// QMode == 0 or QMode == 2
_EntryList = w ;
ObjectWaiter * q = NULL ;
ObjectWaiter * p ;
for (p = w ; p != NULL ; p = p->_next) {
guarantee (p->TState == ObjectWaiter::TS_CXQ, "Invariant") ;
p->TState = ObjectWaiter::TS_ENTER ;
p->_prev = q ;
q = p ;
}
}
if (_succ != NULL) continue;
w = _EntryList ;
if (w != NULL) {
guarantee (w->TState == ObjectWaiter::TS_ENTER, "invariant") ;
ExitEpilog (Self, w) ;//唤醒unpark _EntryList中等待的线程
return ;
}
}
}
释放的代码逻辑很长,我们主要关注一下几点:
1.QMode是不同的模式下,需要走不同的逻辑策略;
2.重点是_owner线程退出,并且唤醒_EntryList中的线程进行重新竞争锁。
以上就是hotspot-jvm中同步关键字重锁加锁流程,以及源码介绍,部分源码读起来很艰涩,有很多地方没有完全读懂,有兴趣的可以上github自行研究。研究的主要目的是加深jvm锁的概念和原理。下图是多线程锁竞争的大致流程,对象内置锁monitor对象中_cxq和EntryList以及WaitSet三个线程队列的变化过程。关于1.6以后锁升级和wait()/notify()原理,放入下一篇。
参考
jdk7u-hotspot源码:
https://github.com/openjdk-mirror/jdk7u-hotspot;
https://www.cnblogs.com/kundeg/p/8422557.html ,其中部分插图是来源该博客。
本文分享自微信公众号 - MyClass社区(MyClass_ZZ)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。
来源:oschina
链接:https://my.oschina.net/u/4591203/blog/4410901