Java线程Thread的状态解析以及状态转换分析 多线程中篇(七)

时间:2024-07-08 21:37:50
线程与操作系统中线程(进程)的概念同根同源,尽管千差万别。
操作系统中有状态以及状态的切换,Java线程中照样也有。

State

在Thread类中有内部类 枚举State,用于抽象描述Java线程的状态,共有6种不同的状态
Java线程Thread的状态解析以及状态转换分析 多线程中篇(七)
详细定义如下:

public enum State {

/**

* 至今尚未启动的线程的状态。

*/

NEW,

/**

* 可运行线程的线程状态。

* 处于可运行状态的某一线程正在 Java 虚拟机中运行,但它可能正在等待操作系统中的其他资源,比如处理器。

*/

RUNNABLE,

/**

* 受阻塞并且正在等待监视器锁的某一线程的线程状态。

* 处于受阻塞状态的某一线程正在等待进入一个同步的块/方法的监视器锁,或者在调用 Object.wait 之后再次进入同步的块/方法。

*/

BLOCKED,

/**

* 某一等待线程的线程状态。

* 某一线程因为调用下列方法之一而处于等待状态:

* 不带超时值的 Object.wait

* 不带超时值的 Thread.join

* LockSupport.park

* 处于等待状态的线程正等待另一个线程,以执行特定操作。

* 例如,已经在某一对象上调用了 Object.wait() 的线程正等待另一个线程,以便在该对象上调用 Object.notify() 或 Object.notifyAll()。

* 已经调用了 Thread.join() 的线程正在等待指定线程终止。

*/

WAITING,

/**

* 具有指定等待时间的某一等待线程的线程状态。

* 某一线程因为调用以下带有指定正等待时间的方法之一而处于定时等待状态:

* Thread.sleep

* 带有超时值的 Object.wait

* 带有超时值的 Thread.join

* LockSupport.parkNanos

* LockSupport.parkUntil

*/

TIMED_WAITING,

/**

* 已终止线程的线程状态。线程已经结束执行。

*/

TERMINATED;

}

状态详解

NEW

当一个线程创建后,也就是new了一个Thread,那么这个Thread的state就是NEW
有且只有这种情况下,才为NEW,不会从任何状态转换而来
也就是说如果一个线程状态已经不再是NEW,那么他永远不可能再重新回到NEW的状态,这是一个起点
下面的示例中创建了一个线程myThread,并没有调用start方法
Java线程Thread的状态解析以及状态转换分析 多线程中篇(七)

TERMINATED

当一个线程终止后,就进入状态TERMINATED
TERMINATED作为线程的终点,一旦进入TERMINATED状态,将不再能够转换为其他状态
下面的示例中,创建了一个线程myThread,并且调用start方法启动
然后主线程(当前线程)sleep 1秒(确保myThread肯定结束了),然后查看myThread的状态,很显然,此时已经进入终止状态
Java线程Thread的状态解析以及状态转换分析 多线程中篇(七)
NEW和TERMINATED分别对应线程生命周期的起点和终点
对于NEW来说,一旦离开,就永远回不来了;
对于TERMINATED来说,一旦到达, 就永远回不去了;
Java线程Thread的状态解析以及状态转换分析 多线程中篇(七)

RUNNABLE

RUNNABLE用于表示可运行状态
下面的代码在主线程中运行,运行过程中是RUNNABLE状态
Java线程Thread的状态解析以及状态转换分析 多线程中篇(七)
API中有说到:“处于可运行状态的某一线程正在 Java 虚拟机中运行,但它可能正在等待操作系统中的其他资源,比如处理器。”
也就是说一个RUNNABLE并不是一定正在运行
如果我们将线程运行所有的资源与条件分为两种:CPU时间片以及除了时间片以外的所有其他;
一旦进入RUNNABLE状态,那么他肯定已经拥有了“除了时间片以外的所有其他资源”
 但是,是否正在被执行?这个不确定,还要看是否被分配了时间片
如果没有处理器资源(时间片)那么就是“准备妥当”状态,如果分配了处理器资源(时间片),那么就是“正在运行”状态。
所以RUNNABLE状态可以细分为两种状态:准备妥当(READY)RUNNING(正在运行)
Java线程Thread的状态解析以及状态转换分析 多线程中篇(七)
但是,为什么没有将RUNNABLE细分?
很显然,对于开发者来说能够做到的就是“除了时间片以外的所有其他资源”,而对于操作系统处理器CPU时间片的调度,是完全没有能力操控的(yield也只是提示)
所以,从应用的角度看,也就只有RUNNABLE状态,一个RUNNABLE的线程,他随时可能在运行,也可能在等待调度。

等待状态

BLOCKED、WAITING、TIMED_WAITING三种状态相对前面的几种,相对稍微复杂一点,因为会涉及到各种切换
对照着汉字来说,这三者都有“等”的意思,但是却又不太相同
举几个例子感受一下
当你发现前方信号灯转变为红灯时,你停车等待;
当你经过斑马线时,正好有行人经过,你停车等待;
当售票窗口中午休息时,你原地等待;
这几种等待更多的是因为不可抗力,不得不等的一种场景,BLOCKED更接近这种等待;
对于临界资源的访问,需要互斥访问,Java中使用对象监视器作为锁,想要进入同步区域,就需要获得对应的监视器锁
如果获取不到,就需要等待,这就是BLOCKED状态;
要出门时,你老婆说我化个妆,你等我下;
买水果时,售货员说刚卖完了,师傅去仓库去取了,您稍等一下;
此时的等,是在等一件事情的发生,WAITING更接近这种等待;
虽然都是在等待,卡住不能动,还是等一等,还是有区别的;
TIMED_WAITING与WAITING就比较相似了,他们的区别,从汉语的角度理解类似“你等我两分钟和你等一会”的区别
等两分钟有时间,等一会儿不确定到底等待多大一会儿
再回到Java线程中,可以认为:
  • 如果一个线程在等待获取进入同步区域的监视器锁,那么是BLOCKED;
  • 如果线程调用了不带超时值的等待方法,比如 Object.wait,持续等待某件事情的发生,直到收到通知,那么是WAITING
  • 如果线程调用了比如带有超时值的等待方法,比如wait(long timeout),进行一定时间的等待,到到时间后将不再等待,那么是TIMED_WAITING

状态转换图

Java线程Thread的状态解析以及状态转换分析 多线程中篇(七)
换一个角度理解,线程状态的切换
下图从前驱和后继的角度分析了线程状态的变化
以中间一列为中心
Java线程Thread的状态解析以及状态转换分析 多线程中篇(七)

状态对比

既然操作系统中线程概念模型有状态切换,Java线程也有状态,他们有何异同?
Java线程Thread的状态解析以及状态转换分析 多线程中篇(七)
如上图所示,操作系统中的进程、线程模型的状态
核心为就绪(ready)阻塞(waiting)执行(run)
而对于Java线程中
核心状态为RUNNABLE、BLOCKED、WAITING、TIMED_WAITING(项目中根本不会创建线程,会借助于线程池,所以NEW和TERMINATED非重点)
Java线程为操作系统原生线程的映射,状态上也是有所映射的
Runnable状态,则对应了操作系统中的就绪(ready)和执行(run)
TIMED_WAITING ,WAITING还是BLOCKED,对应的都是操作系统线程的阻塞(waiting)状态
需要注意的是:这些状态是虚拟机状态,它不反映任何操作系统的线程状态,可以查看State的注释
Java线程Thread的状态解析以及状态转换分析 多线程中篇(七)

为什么状态没有对应?

我们之前在提及线程的实现时,就有说到用户级和内核支持的对比,内核支持的是依靠操作系统来调度的,1.2之后就是对操作系统线程的映射
所以,既然调度依赖的是操作系统,那么,操作系统底层的状态对于开发者来说就不是那么必要了,因为你并不能对他进行事无巨细的控制
JVM中的线程是操作系统底层线程的映射,既然是映射,可以认为是一个薄层封装
封装的目的是为了更好的符合Java多线程编程的模型,而不是要原模原样的去照搬
从这一点也能更好地理解,为什么RUNNABLE相当于READY和RUNNING,因为JVM本来就只能做到这一步,READY还是RUNNING,搞不了,那还提供这两个状态干什么呢?
所以记住:
JVM中的状态只是Java的多线程模型中的状态,并不反应任何操作系统的线程状态
JVM中的状态与底层操作系统中线程的状态也没有必要去映射