jvm并发之锁类别
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的markWordconst 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: inflatedif (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放回线程的omInFreeListomRelease (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 monitorm->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 statem->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 monitorif (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-opmarkOop 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的markWordassert (!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关联(轻量级锁)。解锁过程如轻量级锁。

各种锁的状态转换图:

http://blog.csdn.net/hffhjh111/article/details/53140909(window的Mutex介绍)
附件列表
浙公网安备 33010602011771号