刨根问底Java多线程系列(1):线程状态

来源:互联网 发布:银行大数据精准营销 编辑:程序博客网 时间:2024/05/17 01:36

一、引言

首先说明,本文讨论的是Hotspot的6u23版本,锁是synchronized实现而不是Lock。
在看《Java多线程编程核心技术》时,读到下面这段(P143,3.1.3节最后一段):

“每个锁对象都有两个队列,一个是就绪队列,一个是阻塞队列。就绪队列存储了将要获得锁的线程,阻塞队列存储了被阻塞的线程。一个线程被唤醒后,才会进入就绪队列,等待CPU的调度;反之,一个线程被wait后,就会进入阻塞队列,等待下一次被唤醒。”

再贴上线程状态转换图【图1】
这里写图片描述

问题来了:

 就绪队列与阻塞队列分别存放的是什么状态的线程?

可能有同学看到阻塞队列,就会认为阻塞队列里面存放的都是Blocked状态的线程。那请问:Waiting状态的线程在哪里?是在就绪队列里面吗?

请看状态与队列关系图【图2】
(引自深入JVM锁机制1-synchronized)
这里写图片描述

根据我一贯不爱卖关子的风格,我告诉你答案:

  1. 阻塞队列(Blocking Queue)里面存放Waiting和Timed Waiting状态的线程

  2. Contention List:所有请求锁的线程将被首先放置到该竞争队列,Blocked状态线程在这里

  3. Entry List:Contention List中那些有资格成为候选人的线程被移到Entry List,Runnable状态线程在这里

  4. Owner线程在unlock时会从ContentionList中迁移线程到EntryList,并会指定EntryList中的某个线程(一般为Head)为Ready(OnDeck)线程。Owner线程并不是把锁传递给OnDeck线程,只是把竞争锁的权利交给OnDeck,OnDeck线程需要重新竞争锁。这样做虽然牺牲了一定的公平性,但极大的提高了整体吞吐量,在Hotspot中把OnDeck的选择行为称之为“竞争切换”。OnDeck线程获得锁后即变为owner线程,无法获得锁则会依然留在EntryList中,考虑到公平性,在EntryList中的位置不发生变化(依然在队头)。如果Owner线程被wait方法阻塞,则转移到WaitSet队列;如果在某个时刻被notify/notifyAll唤醒,则再次转移到EntryList。

注意:

     sleep方法调用后,该线程是处于Timed waiting状态,但是并不放弃该锁。

【参考《Java多线程编程核心技术》3.2.5节”方法join(long)与sleep(long)区别“,P184】

二、概念澄清

我曾就Java状态转换问题上网查过资料,发现有些帖子上会出现一些误区,在此一并澄清:

  1. 线程细分有7种状态:New、Blocked、Waiting、Timed Waiting、Terminated、Runnable和Running。大体上,Runnable包括【图1】中的Ready;对于停滞状态,由于suspend与resume方法都是Java不建议使用的,所以其状态不在“正常“状态里

  2. 要是帖子中提到“阻塞状态”,指的是Waiting、Timed Waiting以及停滞状态,而不是指Blocked

  3. Blocked状态指的是线程想要获得锁,但是锁被其他线程占有,因此线程就变成等待获取锁的Blocked状态【参考《深入理解Java虚拟机:JVM高级特性与最佳实践》P340】;调用sleep进入的是Timed Waiting状态,而不是Blocked状态(这在“线程状态转换图”【图1】中就能看到)

三、与Blocked有关的事儿

现在再问一个问题:

如果在1个锁的阻塞队列中同时存在N个Blocked线程(N>1),这时锁被释放了,应该是将这N个Blocked线程同时唤醒放入就绪队列,还是本次只唤醒1个Blocked线程放入就绪队列?

很遗憾地告诉大家,由于我没有找到相关资料,因此不能像上面给予确定的答案,可能不同版本的JVM有不同的实现。不过,我们可以通过推理得到较为合适的答案。当1个线程A通过start方法启动时,首先是进入Runnable状态,如果此时锁L被线程B占有,则A进入Blocked状态并进入Contention List。这里有个疑惑:

   为什么不把Blocked的线程放入Entry List?

为什么有这个疑惑。因为Blocked线程迟早要进入Entry List,而不像Waiting线程需要notify,干嘛不直接放入Entry List?原因就在于Entry List里面放的不仅是从Blocked转换为Runnable来的线程,同时可能有Waiting、Timed waiting以及停滞状态转换为Runnable的线程。你把Blocked线程直接扔到Entry List里,Contention List还有个毛用。通俗地说:

       Entry List不只是为你Blocked线程准备的

所以,本节开头的问题答案就出来了:为了保证公平起见,1次只唤醒1个Blocked线程放入Entry List。更何况被挂起的(如Blocked)线程被唤醒需要消耗不小的资源。【参考《Java并发编程实战》13.3节公平性,P233】

参考文献

chen77716:深入JVM锁机制1-synchronized
《Java多线程编程核心技术》
《Java并发编程实战》
《深入理解Java虚拟机:JVM高级特性与最佳实践》

0 0