线程BLOCKED
Posted xiaofan156
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线程BLOCKED相关的知识,希望对你有一定的参考价值。
此时的线程C无法进入synchronized{}代码块,用jstack看应该是BLOCKED状态,如下图:
我们看看monitorenter指令对应的源码吧,位置:openjdk/hotspot/src/share/vm/interpreter/interpreterRuntime.cpp
1 IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorenter(JavaThread* thread, BasicObjectLock* elem)) 2 #ifdef ASSERT 3 thread->last_frame().interpreter_frame_verify_monitor(elem); 4 #endif 5 if (PrintBiasedLockingStatistics) { 6 Atomic::inc(BiasedLocking::slow_path_entry_count_addr()); 7 } 8 Handle h_obj(thread, elem->obj()); 9 assert(Universe::heap()->is_in_reserved_or_null(h_obj()), 10 "must be NULL or an object"); 11 if (UseBiasedLocking) { 12 // Retry fast entry if bias is revoked to avoid unnecessary inflation 13 ObjectSynchronizer::fast_enter(h_obj, elem->lock(), true, CHECK); 14 } else { 15 ObjectSynchronizer::slow_enter(h_obj, elem->lock(), CHECK); 16 } 17 assert(Universe::heap()->is_in_reserved_or_null(elem->obj()), 18 "must be NULL or an object"); 19 #ifdef ASSERT 20 thread->last_frame().interpreter_frame_verify_monitor(elem); 21 #endif 22 IRT_END
上面的代码有个if (UseBiasedLocking)判断,是判断是否使用偏向锁的,本例中的锁显然已经不属于当前线程C了,所以我们还是直接看slow_enter(h_obj, elem->lock(), CHECK)方法吧;
打开openjdk/hotspot/src/share/vm/runtime/synchronizer.cpp:
1 void ObjectSynchronizer::slow_enter(Handle obj, BasicLock* lock, TRAPS) { 2 markOop mark = obj->mark(); 3 assert(!mark->has_bias_pattern(), "should not see bias pattern here"); 4 5 //是否处于无锁状态 6 if (mark->is_neutral()) { 7 // Anticipate successful CAS -- the ST of the displaced mark must 8 // be visible <= the ST performed by the CAS. 9 lock->set_displaced_header(mark); 10 //无锁状态就去竞争锁 11 if (mark == (markOop) Atomic::cmpxchg_ptr(lock, obj()->mark_addr(), mark)) { 12 TEVENT (slow_enter: release stacklock) ; 13 return ; 14 } 15 // Fall through to inflate() ... 16 } else 17 if (mark->has_locker() && THREAD->is_lock_owned((address)mark->locker())) { 18 //如果处于有锁状态,就检查是不是当前线程持有锁,如果是当前线程持有的,就return,然后就能执行同步代码块中的代码了 19 assert(lock != mark->locker(), "must not re-lock the same lock"); 20 assert(lock != (BasicLock*)obj->mark(), "don‘t relock with same BasicLock"); 21 lock->set_displaced_header(NULL); 22 return; 23 } 24 25 #if 0 26 // The following optimization isn‘t particularly useful. 27 if (mark->has_monitor() && mark->monitor()->is_entered(THREAD)) { 28 lock->set_displaced_header (NULL) ; 29 return ; 30 } 31 #endif 32 33 // The object header will never be displaced to this lock, 34 // so it does not matter what the value is, except that it 35 // must be non-zero to avoid looking like a re-entrant lock, 36 // and must not look locked either. 37 lock->set_displaced_header(markOopDesc::unused_mark()); 38 //锁膨胀 39 ObjectSynchronizer::inflate(THREAD, obj())->enter(THREAD); 40 }
线程C在上面代码中的执行顺序如下:
判断是否是无锁状态,如果是就通过Atomic::cmpxchg_ptr去竞争锁;
不是无锁状态,就检查当前锁是否是线程C持有;
不是线程C持有,调用inflate方法开始锁膨胀;
ObjectSynchronizer::inflate(THREAD, obj())->enter(THREAD);
来看看锁膨胀的源码:
如上图,锁膨胀的代码太长,我们这里只看关键代码吧:
红框中,如果当前状态已经是重量级锁,就通过mark->monitor()方法取得ObjectMonitor指针再返回;
绿框中,如果还不是重量级锁,就检查是否处于膨胀中状态(其他线程正在膨胀中),如果是膨胀中,就调用ReadStableMark方法进行等待,ReadStableMark方法执行完毕后再通过continue继续检查,ReadStableMark方法中还会调用os::NakedYield()释放CPU资源;
如果红框和绿框的条件都没有命中,目前已经是轻量级锁了(不是重量级锁并且不处于锁膨胀状态),可以开始膨胀了,如下图:
简单来说,锁膨胀就是通过CAS将监视器对象OjectMonitor的状态设置为INFLATING,如果CAS失败,就在此循环,再走前一副图中的的红框和绿框中的判断,如果CAS设置成功,会继续设置ObjectMonitor中的header、owner等字段,然后inflate方法返回监视器对象OjectMonitor;
看看之前slow_enter方法中,调用inflate方法的代码如下:
1 ObjectSynchronizer::inflate(THREAD, obj())->enter(THREAD);
所以inflate方法返回监视器对象OjectMonitor之后,会立刻执行OjectMonitor的enter方法,这个方法中开始竞争锁了,方法在openjdk/hotspot/src/share/vm/runtime/objectMonitor.cpp文件中:
如上图,红框中表示OjectMonitor的enter方法一进来就通过CAS将OjectMonitor的_owner设置为当前线程,绿框中表示设置成功的逻辑,第一个if表示重入锁的逻辑,第二个if表示第一次设置_owner成功,都意味着竞争锁成功,而我们的线程C显然是竞争失败的,会进入下图中的无线循环,反复调用EnterI方法:
进入EnterI方法看看:
如上图,首先构造一个ObjectWaiter对象node,后面的for(;;)代码块中来是一段非常巧妙的代码,同一时刻可能有多个线程都竞争锁失败走进这个EnterI方法,所以在这个for循环中,用CAS将_cxq地址放入node的_next,也就是把node放到_cxq队列的首位,如果CAS失败,就表示其他线程把node放入到_cxq的首位了,所以通过for循环再放一次,只要成功,此node就一定在最新的_cxq队列的首位。
接下来的代码又是一个无限循环,如下图:
从上图可以看出,进入循环后先调用TryLock方法竞争一次锁,如果成功了就退出循环,否则就调用Self->_ParkEvent->park方法使线程挂起,这里有自旋锁的逻辑,也就是park方法带了时间参数,就会在挂起一段时间后自动唤醒,如果不是自旋的条件,就一直挂起等待被其他条件唤醒,线程被唤醒后又会执行TryLock方法竞争一次锁,竞争不到继续这个for循环;
到这里我们已经把线程C在BLOCK的时候的逻辑理清楚了,小结如下:
1. 偏向锁逻辑,未命中;
2. 如果是无锁状态,就通过CAS去竞争锁,此处由于锁已经被线程B持有,所以不是无锁状态;
3. 不是无锁状态,而且锁不是线程C持有,执行锁膨胀,构造OjectMonitor对象;
4. 竞争锁,竞争失败就将线程加入_cxq队列的首位;
5. 开始无限循环,竞争锁成功就退出循环,竞争失败线程挂起,等待被唤醒后继续竞争;
————————————————
版权声明:本文为CSDN博主「程序员欣宸」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/boling_cavalry/article/details/77793224
以上是关于线程BLOCKED的主要内容,如果未能解决你的问题,请参考以下文章
Java线程状态中BLOCKED和WAITING有什么差别?
转:java线程状态说明,Jstack线程状态BLOCKED/TIMED_WAITING/WAITING解释
Java 线程状态转换,WAITING 到 BLOCKED,还是 RUNNABLE?
Java:两个 WAITING + 一个 BLOCKED 线程, notify() 导致活锁, notifyAll() 没有,为啥?