线程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解释

WAIT 和 BLOCKED 线程状态的区别

sql server 怎么查看blocked的线程

Java 线程状态转换,WAITING 到 BLOCKED,还是 RUNNABLE?

Java:两个 WAITING + 一个 BLOCKED 线程, notify() 导致活锁, notifyAll() 没有,为啥?