背景
掌握多线程,并发,锁是一个优秀的程序员必备的知识,他们都是基于线程的而有意义,熟悉并且理解线程的机制是非常重要的。
今天我们来聊一聊,线程有几个状态?
如果你去网上冲浪一下,会发现各说纷纭,5 大状态、6 大状态、7 大状态、新建、就绪、可执行、运行、阻塞、锁池、挂起、中断、等待、结束、死亡、停滞,看到这些名词,你一定晕了吧?
JAVA 定义的状态
如果你打开 Thread 类,找到他下面的枚举类 State,你会发现 JAVA 只定义了以下 6 种状态:
- New(新建)
- Runnable(运行)
- Blocked(阻塞)
- Waitting(无限期等待)
- Timed Watting(限期等待)
- Terminated(终止)
这里的状态名词翻译参考了 周志明的《深入理解 Java 虚拟机》
下面我会对这些状态进行逐一分析,并且解释为什么出现上面那么多的状态
New(新建)
新创建了一个线程对象,但还没有调用 start()方法。
Runnable(运行)
Runnable 包括了操作系统线程中的 Running(运行中)和 Ready(就绪)
- Ready(就绪)
线程对象创建后,其他线程(比如 main 线程)调用了该对象的 start()方法
该状态的线程位于可运行线程池中,等待被线程调度选中,获取 CPU 的使用权,也就是说有了被 CPU 运行的资格
- Running(运行中) 就绪状态的线程在获得 CPU 时间片后转换为运行中(Running)
Blocked(阻塞)
在等待着获取一个排它锁(例如:synchronized),这个事件将在另外一个线程放弃这个锁的时候发生
Waitting(无限期等待)
这种状态的线程不会被分配 CPU 执行时间,不给过无需等待被他其他线程显示地唤醒,在一定时间之后他们会被系统自动唤醒
以下方法会让线程进入无限期等待状态
- 没有设置 Timeout 参数的 Object.wait()
- 没有设置 Timeout 参数的 Thread.join()
Timed Waitting(限期等待)
这种状态的线程不会被分配 CPU 执行时间,不给过无需等待被他其他线程显示地唤醒,在一定时间之后他们会被系统自动唤醒
以下方法会让线程进入限期等待状态
- Thread.sleep()
- 设置了 Timeout 参数的 Object.wait()
- 设置了 Timeout 参数的 Thread.join()
- LockSupport.parkNanos()方法
- LockSupport.parkUnit()方法
Terminated(终止)
已终止的线程状态,线程已经终止运行
状态转换
- 下图是《深入理解 JAVA 虚拟机》线程的状态转换图 上面这张图比较好理解,但是没有暴露太多运行状态的细节
- 我们来看下一个图 这张图把运行状态中的(运行中和就绪的状态的转换)画了出来,这个内部状态是 CPU 去调度的。没有显性的在 JAVA 状态枚举里定义
相关容易混淆的问题解答
造成那么多名词的原因是什么?
- 名词翻译问题
- 比如 Terminated 翻译为终止,但是他的意义跟死亡有点类似,大家就传着传者名词叫 Dead 了(死亡)
- 含义混淆
- 例如 Runnable 里的内部状态 Ready 和 Running,这两个状态是 CPU 调度去关心的,但是枚举里想屏蔽这个东西。
- 例如 Thread.suspend()这个方法,翻译过来就是挂起,有的同学就说线程有一个挂起状态,这明显就是把一个行为当成一个状态,没有理解线程的状态
什么是锁池?什么等待池?什么是等待队列?什么是同步队列?这几个有什么区别?
大家自己 Thinking 一下,假如现在有 10 个线程,现在有一个同步代码块的方法,同时只能有一个线程进入对吧?那其他 9 个线程都是阻塞状态, 那这 9 个线程放在哪里呢?
当第一个线程运行完之后,其他的阻塞的线程可是要竞争的,对吧?所以我们这边说阻塞的线程是放到锁池里的,那么这个锁池是怎么实现的呢? 这么一想就能明白了,它的内部是一个队列,也就叫同步队列。
同时类比一下,什么叫等待池,等待队列?其实跟之前的锁池,同步队列一个道理。
这里我并没有在 Google上找到关于锁池和同步队列比较官方的东西,很多资料也是一带而过,鸟大的可以给小弟普及下。
为什么有的博客里,把 Time Waiting、Waitting、Blocked 这三个状态统一成 Blocked,这种说话对么?
如果我们换一种角度来想问题,当线程状态是 Time Waiting 的时候,线程有干活吗?Waitting 的时候线程有干活吗?Blocked 线程有干活吗?
没错有人说了,Wait/Notify 是 Waitting 状态的转换,Synchronized 是 Blocked 状态的转换?但是他实质上不都是线程不干活吗?
所以我认为上面那个问题,其实还是有点道理的,那么JAVA为什么要把这几个状态区分呢?
在B乎找到了一个答案看起来还以自恰一点。原文在这里,赵老师的回答
这里我简单摘要一下
- blocked 是过去分词,意味着他是被卡住的(无辜啊,全是泪)。因为这段代码只让一条线程运行。同时,jvm 是知道怎么结束 blocked 的,只要别的线程退出这段代码,他就会自动让你进去。也就是说别的线程无需唤醒你,由 jvm 自动来干。
- waiiting 是说我调用 wait()等函数,主动卡住自己(我在等一个白富美),请 jvm 在满足某种条件后(白富美发消息让我们晚上见),比如另条线程调用了 notify()后,把我唤醒。这个唤醒的责任在于别的线程(白富美)明确的调用一些唤醒函数。
做这样的区分,是 JVM 出于管理的需要,做了这种区分,比如两个原因的线程放两个队列里管理(上面讲到的同步队列和等待队列),如果别的线程运行出了 synchronized 这段代码,我只需要去Blocked队列,放个出来。而某人调用了 notify(),我只需要去 Waitting 队列里取个出来。
文章参考:
- 线程状态解释参考 《深入理解JAVA虚拟机》-周志明
- 第二张线程状态转换图来自:https://blog.csdn.net/pange1991/article/details/53860651/