刨根问底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)
根据我一贯不爱卖关子的风格,我告诉你答案:
阻塞队列(Blocking Queue)里面存放Waiting和Timed Waiting状态的线程
Contention List:所有请求锁的线程将被首先放置到该竞争队列,Blocked状态线程在这里
Entry List:Contention List中那些有资格成为候选人的线程被移到Entry List,Runnable状态线程在这里
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状态转换问题上网查过资料,发现有些帖子上会出现一些误区,在此一并澄清:
线程细分有7种状态:New、Blocked、Waiting、Timed Waiting、Terminated、Runnable和Running。大体上,Runnable包括【图1】中的Ready;对于停滞状态,由于suspend与resume方法都是Java不建议使用的,所以其状态不在“正常“状态里
要是帖子中提到“阻塞状态”,指的是Waiting、Timed Waiting以及停滞状态,而不是指Blocked
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高级特性与最佳实践》
- 刨根问底Java多线程系列(1):线程状态
- 刨根问底Java多线程系列(2):线程不安全的最根本的原因是什么
- 刨根问底Java多线程系列:线程不安全的最根本的原因是什么
- Java 多线程、并发系列之线程定义和线程状态
- Java多线程系列1(线程基本常识)
- Java多线程:线程状态
- java多线程-线程状态
- Java多线程:线程状态
- Java多线程:线程状态
- Java多线程:线程状态
- Java多线程:线程状态
- Java多线程:线程状态
- Java多线程:线程状态
- Java多线程:线程状态
- Java多线程:线程状态
- Java多线程:线程状态
- Java多线程:线程状态
- Java多线程:线程状态
- MyEclipse调试maven第三方jar包源码
- 端口渗透总结
- WCF使用安全证书验证消息加密
- 如何在互联网环境下生存下去?
- 浅析extendedLayout, automaticallyAdjustsScrollViewInsets, extendedLayoutIncludesOpaqueBars
- 刨根问底Java多线程系列(1):线程状态
- DEV 中 gridControl1添加checkbox 列
- 敏捷项目开发中的需求分析
- ChemDraw 15.1 Pro怎么绘制彩色结构
- 强悍的命令行 —— 命令行域名的解析
- lingo解题报告内容解释
- vim编辑器
- iOS数据存储方式
- ubuntu环境搭建