I、java同步锁
重量级锁具有很大互斥性,线程的堵塞和唤醒都需要从用户态Ring3到内核态Ring0,频繁的切换会加重cpu的负担。传统的synchronized属于重量级锁,其实现原理基于对Mutex锁的调用,在每个对象的对象头都有一个指向Monitor的指针,线程在执行同步代码块的时候,会先执行MonitorEnter,获取Monitor的锁,退出时执行MonitorExit,同步方法则在方法描述的flag中添加一个ACC_SYNCHRONIZED,原理和Monitor差不多。重量级锁的实现机制:每个线程都维护着一个私有的Minitor Record组,每一个被锁住的对象都会和一个monitor record相关联,而对象头中的LockWord会指向关联的monitorRecord的起始位置。monitor record的结构为如下:
Owner:若为null,则表示没有线程占用锁,若不为空,则表示拥有改对象锁的线程的标识。
EntryQ:关联一个互斥锁,阻塞所有试图获得锁的线程。
Rcthis:阻塞的线程个数。
Nest:实现重入锁的计数。
Candidate:0表示没有需要唤醒的线程,1表示需要唤醒一个线程来竞争锁。
下图为java对象头中的markWord:
从图可以看出一共有5种状态:
001表示无锁状态,bitfields存储的是对象的hashCode,age等。
101表示偏向锁,threadID初始值为null,当有线程获得锁时,其值就是线程的标识。
00标识轻量级锁,其bitfields指向线程栈中的lock Record空间。
10表示重量级互斥锁,bitfields指向monitor,11表示GC垃圾回收标识。
轻量级锁的实现利用操作系统的CAS(compare and swap),避免去进入monitor,汇编执行如 CMPXCHG。
获取轻量锁过程:
判断对象是否处于无锁状态,若是则在线程栈中年创建锁记录,保存Object Header的markWord和指向Object Header的指针,并尝试通过CAS将锁记录的指针修改到ObjectHeader的markWord,若成功修改为00(轻锁)状态。
如果对象处于被锁状态,CAS失败,判断Object的MarkWord中的指针是否指向当前线程的锁记录,若是则表明这是一个递归加锁,则初始化锁记录为0,而不是ObjectHeader的markWord。若不是膨胀为重量级锁,修改ObjectMarkword指向线程的monitor Record,并修改状态为10。
解锁过程:
CAS替换MarkWord,成功则解锁成功。失败则表示有另外一个线程竞争,释放锁唤醒阻塞线程,对于已经膨胀为重量级锁,则不降级,即不会降级为轻量级锁。
轻量级锁流程:
分析在openJdk7中的代码:
hotspot目录下的Synchronizer.cpp类中,轻量级进入同步如下:
下面是锁膨胀的过程:
ObjectMonitor * ATTR ObjectSynchronizer::inflate (Thread * Self, oop object) {
// Inflate mutates the heap ...
// Relaxing assertion for bug 6320749.
assert (Universe::verify_in_progress() ||
!SafepointSynchronize::is_at_safepoint(), "invariant") ;
for (;;) {
//获取object的markWord
const markOop mark = object->mark() ;
assert (!mark->has_bias_pattern(), "invariant") ;
// The mark can be in one of the following states:
// * Inflated - just return
// * Stack-locked - coerce it to inflated
// * INFLATING - busy wait for conversion to complete
// * Neutral - aggressively inflate the object.
// * BIASED - Illegal. We should never see this
// CASE: inflated
if (mark->has_monitor()) { //若是已经膨胀为重量级锁,则返回
ObjectMonitor * inf = mark->monitor() ;
assert (inf->header()->is_neutral(), "invariant");
assert (inf->object() == object, "invariant") ;
assert (ObjectSynchronizer::verify_objmon_isinpool(inf), "monitor is invalid");
return inf ;
}
// CASE: inflation in progress - inflating over a stack-lock.
// Some other thread is converting from stack-locked to inflated.
// Only that thread can complete inflation -- other threads must wait.
// The INFLATING value is transient.
// Currently, we spin/yield/park and poll the markword, waiting for inflation to finish.
// We could always eliminate polling by parking the thread on some auxiliary list.
if (mark == markOopDesc::INFLATING()) { //正在膨胀,并等待膨胀完毕
TEVENT (Inflate: spin while INFLATING) ;
ReadStableMark(object) ;
continue ;
}
// CASE: stack-locked
// Could be stack-locked either by this thread or by some other thread.
//
// Note that we allocate the objectmonitor speculatively, _before_ attempting
// to install INFLATING into the mark word. We originally installed INFLATING,
// allocated the objectmonitor, and then finally STed the address of the
// objectmonitor into the mark. This was correct, but artificially lengthened
// the interval in which INFLATED appeared in the mark, thus increasing
// the odds of inflation contention.
//
// We now use per-thread private objectmonitor free lists.
// These list are reprovisioned from the global free list outside the
// critical INFLATING...ST interval. A thread can transfer
// multiple objectmonitors en-mass from the global free list to its local free list.
// This reduces coherency traffic and lock contention on the global free list.
// Using such local free lists, it doesn't matter if the omAlloc() call appears
// before or after the CAS(INFLATING) operation.
// See the comments in omAlloc().
//若没有线程膨胀锁,线程开始膨胀锁
if (mark->has_locker()) { //若有线程锁,证明存在竞争线程
ObjectMonitor * m = omAlloc (Self) ;//从线程local中omInFreeList获取到一个可用的monitor,若omInFreeList没有则从全局gFreeList中获取,再没有的话new ObjectMonitor。
// Optimistically prepare the objectmonitor - anticipate successful CAS
// We do this before the CAS in order to minimize the length of time
// in which INFLATING appears in the mark.
m->Recycle();
m->_Responsible = NULL ;
m->OwnerIsThread = 0 ;
m->_recursions = 0 ;
m->_SpinDuration = ObjectMonitor::Knob_SpinLimit ; // Consider: maintain by type/class
//CAS修改markWord(0值),修改为正在膨胀状态
markOop cmp = (markOop) Atomic::cmpxchg_ptr (markOopDesc::INFLATING(), object->mark_addr(), mark) ;
if (cmp != mark) { //失败再次尝试
//将monitor放回线程的omInFreeList
omRelease (Self, m, true) ;
continue ; // Interference -- just retry
}
// We've successfully installed INFLATING (0) into the mark-word.
// This is the only case where 0 will appear in a mark-work.
// Only the singular thread that successfully swings the mark-word
// to 0 can perform (or more precisely, complete) inflation.
//
// Why do we CAS a 0 into the mark-word instead of just CASing the
// mark-word from the stack-locked value directly to the new inflated state?
// Consider what happens when a thread unlocks a stack-locked object.
// It attempts to use CAS to swing the displaced header value from the
// on-stack basiclock back into the object header. Recall also that the
// header value (hashcode, etc) can reside in (a) the object header, or
// (b) a displaced header associated with the stack-lock, or (c) a displaced
// header in an objectMonitor. The inflate() routine must copy the header
// value from the basiclock on the owner's stack to the objectMonitor, all
// the while preserving the hashCode stability invariants. If the owner
// decides to release the lock while the value is 0, the unlock will fail
// and control will eventually pass from slow_exit() to inflate. The owner
// will then spin, waiting for the 0 value to disappear. Put another way,
// the 0 causes the owner to stall if the owner happens to try to
// drop the lock (restoring the header from the basiclock to the object)
// while inflation is in-progress. This protocol avoids races that might
// would otherwise permit hashCode values to change or "flicker" for an object.
// Critically, while object->mark is 0 mark->displaced_mark_helper() is stable.
// 0 serves as a "BUSY" inflate-in-progress indicator.
// fetch the displaced mark from the owner's stack.
// The owner can't die or unwind past the lock while our INFLATING
// object is in the mark. Furthermore the owner can't complete
// an unlock on the object, either.
markOop dmw = mark->displaced_mark_helper() ;//获取displaced_mark(hashcode,age)
assert (dmw->is_neutral(), "invariant") ;
// Setup monitor fields to proper values -- prepare the monitor
m->set_header(dmw) ;//monitor存储displaced_mark_word
// Optimization: if the mark->locker stack address is associated
// with this thread we could simply set m->_owner = Self and
// m->OwnerIsThread = 1. Note that a thread can inflate an object
// that it has stack-locked -- as might happen in wait() -- directly
// with CAS. That is, we can avoid the xchg-NULL .... ST idiom.
m->set_owner(mark->locker());//设置monitor的拥有者
m->set_object(object);
// TODO-FIXME: assert BasicLock->dhw != 0.
// Must preserve store ordering. The monitor state must
// be stable at the time of publishing the monitor address.
guarantee (object->mark() == markOopDesc::INFLATING(), "invariant") ;
object->release_set_mark(markOopDesc::encode(m));//markWord设置为重量级锁ptr|10
// Hopefully the performance counters are allocated on distinct cache lines
// to avoid false sharing on MP systems ...
if (ObjectMonitor::_sync_Inflations != NULL) ObjectMonitor::_sync_Inflations->inc() ;
TEVENT(Inflate: overwrite stacklock) ;
if (TraceMonitorInflation) {
if (object->is_instance()) {
ResourceMark rm;
tty->print_cr("Inflating object " INTPTR_FORMAT " , mark " INTPTR_FORMAT " , type %s",
(intptr_t) object, (intptr_t) object->mark(),
Klass::cast(object->klass())->external_name());
}
}
return m ;
}
// CASE: neutral
// TODO-FIXME: for entry we currently inflate and then try to CAS _owner.
// If we know we're inflating for entry it's better to inflate by swinging a
// pre-locked objectMonitor pointer into the object header. A successful
// CAS inflates the object *and* confers ownership to the inflating thread.
// In the current implementation we use a 2-step mechanism where we CAS()
// to inflate and then CAS() again to try to swing _owner from NULL to Self.
// An inflateTry() method that we could call from fast_enter() and slow_enter()
// would be useful.
//线程2在开始膨胀锁时,线程1已经解锁的情况下,创建膨胀锁
assert (mark->is_neutral(), "invariant");
ObjectMonitor * m = omAlloc (Self) ;
// prepare m for installation - set monitor to initial state
m->Recycle();
m->set_header(mark);
m->set_owner(NULL);
m->set_object(object);
m->OwnerIsThread = 1 ;
m->_recursions = 0 ;
m->_Responsible = NULL ;
m->_SpinDuration = ObjectMonitor::Knob_SpinLimit ; // consider: keep metastats by type/class
//CAS monitor
if (Atomic::cmpxchg_ptr (markOopDesc::encode(m), object->mark_addr(), mark) != mark) {
m->set_object (NULL) ;
m->set_owner (NULL) ;
m->OwnerIsThread = 0 ;
m->Recycle() ;
omRelease (Self, m, true) ;
m = NULL ;
continue ;
// interference - the markword changed - just retry.
// The state-transitions are one-way, so there's no chance of
// live-lock -- "Inflated" is an absorbing state.
}
// Hopefully the performance counters are allocated on distinct
// cache lines to avoid false sharing on MP systems ...
if (ObjectMonitor::_sync_Inflations != NULL) ObjectMonitor::_sync_Inflations->inc() ;
TEVENT(Inflate: overwrite neutral) ;
if (TraceMonitorInflation) {
if (object->is_instance()) {
ResourceMark rm;
tty->print_cr("Inflating object " INTPTR_FORMAT " , mark " INTPTR_FORMAT " , type %s",
(intptr_t) object, (intptr_t) object->mark(),
Klass::cast(object->klass())->external_name());
}
}
return m ;
}
}
锁膨胀过程:
退出锁:
void ObjectSynchronizer::fast_exit(oop object, BasicLock* lock, TRAPS) {
assert(!object->mark()->has_bias_pattern(), "should not see bias pattern here");
// if displaced header is null, the previous enter is recursive enter, no-op
markOop dhw = lock->displaced_header();
markOop mark ;
if (dhw == NULL) { //递归加锁的在slowEnter里已经把lockRecord设置为null
// Recursive stack-lock.
// Diagnostics -- Could be: stack-locked, inflating, inflated.
mark = object->mark() ;//获取object的markWord
assert (!mark->is_neutral(), "invariant") ;
if (mark->has_locker() && mark != markOopDesc::INFLATING()) { //判断是否有lock并且当前锁是否正在膨胀
assert(THREAD->is_lock_owned((address)mark->locker()), "invariant") ;
}
if (mark->has_monitor()) { //判断markRecord是否有monitor重量级锁
ObjectMonitor * m = mark->monitor() ;
assert(((oop)(m->object()))->mark() == mark, "invariant") ;
assert(m->is_entered(THREAD), "invariant") ;//
}
return ;
}
mark = object->mark() ;
// If the object is stack-locked by the current thread, try to
// swing the displaced header from the box back to the mark.
//不存在线程竞争,线程CAS将lockRecord和object的markWord交换
if (mark == (markOop) lock) {
assert (dhw->is_neutral(), "invariant") ;
if ((markOop) Atomic::cmpxchg_ptr (dhw, object->mark_addr(), mark) == mark) {
TEVENT (fast_exit: release stacklock) ;
return;
}
}
ObjectSynchronizer::inflate(THREAD, object)->exit (THREAD) ;//释放锁
}
III、偏向锁
偏向锁:与轻量级锁比较,因为cas操作任然存在的一定的开销,故出现了偏向锁。只有第一个线程CAS成功才能把线程id假如到markWord中,这时候可以叫该对象偏向于该线程。当该线程再次去获得锁时不需要执行CAS操作更新markWork,虽然堆栈上的锁记录未被初始化,但其对于对象是偏向的,故不需要校验。当存在另外一个线程在该对象同步时,需要撤销偏向锁,到达安全点(safePoint)时(线程暂停)遍历获得偏向锁的线程堆栈,调整锁记录和Object的markWord关联(轻量级锁)。解锁过程如轻量级锁。
各种锁的状态转换图:
参考资料:
(window的Mutex介绍)
附件列表