线程的 6 种状态
在 Java 中线程的生命周期中一共有 6 种状态。
- New(新创建)
- Runnable(可运行)
- Blocked(被阻塞)
- Waiting(等待)
- Timed Waiting(计时等待)
- Terminated(被终止)
如果想要确定线程当前的状态,可以通过 getState() 方法,并且线程在任何时刻只可能处于 1 种状态。
New 新创建
如图所示,首先来看下 New 状态。
New 表示线程被创建但尚未启动的状态:当我们用 new Thread() 新建一个线程时,如果线程没有开始运行 start() 方法,所以也没有开始执行 run() 方法里面的代码,那么此时它的状态就是 New。而一旦线程调用了 start(),它的状态就会从 New 变成 Runnable,也就是状态转换图中中间的这个大方框里的内容。
Runnable 可运行
Java 中的 Runable 状态对应操作系统线程状态中的两种状态,分别是 Running 和 Ready,也就是说,Java 中处于 Runnable 状态的线程有可能正在执行,也有可能没有正在执行,正在等待被分配 CPU 资源。
所以,如果一个正在运行的线程是 Runnable 状态,当它运行到任务的一半时,执行该线程的 CPU 被调度去做其他事情,导致该线程暂时不运行,它的状态依然不变,还是 Runnable,因为它有可能随时被调度回来继续执行任务。
阻塞状态
Runnable 下面的三个方框,它们统称为阻塞状态,在 Java 中阻塞状态通常不仅仅是 Blocked,实际上它包括三种状态,分别是 Blocked(被阻塞)、Waiting(等待)、Timed Waiting(计时等待),这三 种状态统称为阻塞状态,下面我们来看看这三种状态具体是什么含义。
Blocked 被阻塞
首先来看最简单的 Blocked,从箭头的流转方向可以看出,从 Runnable 状态进入 Blocked 状态只有一种可能,就是进入 synchronized 保护的代码时没有抢到 monitor 锁,无论是进入 synchronized 代码块,还是 synchronized 方法,都是一样。
当处于 Blocked 的线程抢到 monitor 锁,就会从 Blocked 状态回到Runnable 状态。
Waiting 等待
我们再看看 Waiting 状态,线程进入 Waiting 状态有三种可能性。
- 没有设置 Timeout 参数的 Object.wait() 方法。
- 没有设置 Timeout 参数的 Thread.join() 方法。
- LockSupport.park() 方法。
Blocked 仅仅针对 synchronized monitor 锁,可是在 Java 中还有很多其他的锁,比如 ReentrantLock,如果线程在获取这种锁时没有抢到该锁就会进入 Waiting 状态,因为本质上它执行了 LockSupport.park() 方法,所以会进入 Waiting 状态。同样,Object.wait() 和 Thread.join() 也会让线程进入 Waiting 状态。
Blocked 与 Waiting 的区别是 Blocked 在等待其他线程释放 monitor 锁,而 Waiting 则是在等待某个条件,比如 join 的线程执行完毕,或者是 notify()/notifyAll() 。
Timed Waiting 限期等待
在 Waiting 上面是 Timed Waiting 状态,这两个状态是非常相似的,区别仅在于有没有时间限制,Timed Waiting 会等待超时,由系统自动唤醒,或者在超时前被唤醒信号唤醒。
以下情况会让线程进入 Timed Waiting 状态。
- 设置了时间参数的 Thread.sleep(long millis) 方法;
- 设置了时间参数的 Object.wait(long timeout) 方法;
- 设置了时间参数的 Thread.join(long millis) 方法;
- 设置了时间参数的 LockSupport.parkNanos(long nanos) 方法和 LockSupport.parkUntil(long deadline) 方法。
讲完如何进入这三种状态,再来看下如何从这三种状态流转到下一个状态。
想要从 Blocked 状态进入 Runnable 状态,要求线程获取 monitor 锁,而从 Waiting 状态流转到其他状态则比较特殊,因为首先 Waiting 是不限时的,也就是说无论过了多长时间它都不会主动恢复。
只有当执行了 LockSupport.unpark(),或者 join 的线程运行结束,或者被中断时才可以进入 Runnable 状态。
如果其他线程调用 notify() 或 notifyAll()来唤醒它,它会直接进入 Blocked 状态,这是为什么呢?
因为唤醒 Waiting 线程的线程如果调用 notify() 或 notifyAll(),要求必须首先持有该 monitor 锁,所以处于 Waiting 状态的线程被唤醒时拿不到该锁,就会进入 Blocked 状态,直到执行了 notify()/notifyAll() 的唤醒它的线程执行完毕并释放 monitor 锁,才可能轮到它去抢夺这把锁,如果它能抢到,就会从 Blocked 状态回到 Runnable 状态。
同样在 Timed Waiting 中执行 notify() 和 notifyAll() 也是一样的道理,它们会先进入 Blocked 状态,然后抢夺锁成功后,再回到 Runnable 状态。
当然对于 Timed Waiting 而言,如果它的超时时间到了且能直接获取到锁/join的线程运行结束/被中断/调用了LockSupport.unpark(),会直接恢复到 Runnable 状态,而无需经历 Blocked 状态。
Terminated 终止
再来看看最后一种状态,Terminated 终止状态,要想进入这个状态有两种可能。
- run() 方法执行完毕,线程正常退出。
- 出现一个没有捕获的异常,终止了 run() 方法,最终导致意外终止。
注意点
- 线程的状态是需要按照箭头方向来走的,比如线程从 New 状态是不可以直接进入 Blocked 状态的,它需要先经历 Runnable 状态。
- 线程生命周期不可逆:一旦进入 Runnable 状态就不能回到 New 状态;一旦被终止就不可能再有任何状态的变化。所以一个线程只能有一次 New 和 Terminated 状态,只有处于中间状态才可以相互转换。
Java线程的生命周期
Java线程在其生命周期中会经历以下几种状态:
-
新建(New) : 当使用
new关键字创建一个Thread对象后,线程处于新建状态。此时,线程尚未启动,不为JVM所知。 -
可运行(Runnable) : 调用线程的
start()方法后,线程进入可运行状态。这个状态包括两种情况:- 尚未分配CPU资源,等待CPU调度执行。
- 已经在CPU上执行,但因为时间片用尽而暂停,等待再次获得CPU资源。
-
运行(Running) : 线程获取到CPU资源,正在执行线程中的代码。
-
阻塞(Blocked) : 线程因为某种原因(如等待锁、等待I/O操作完成等)而暂时不能执行,进入阻塞状态。一旦等待的原因消除,线程可以转换回可运行状态。
-
等待(Waiting) : 线程因为调用了
Object.wait()、Thread.join()无参方法或LockSupport.park()等方法而进入等待状态。这种状态下,线程必须等待其他线程的通知才能继续执行。 -
超时等待(Timed Waiting) : 与等待状态相似,但线程会在指定时间后自动返回到可运行状态,如调用了
Thread.sleep(long millis)、Object.wait(long timeout)、Thread.join(long millis)或LockSupport.parkNanos(long nanos)等方法。 -
终止(Terminated) : 线程执行完毕或因异常退出,进入终止状态,线程生命周期结束。
操作系统中线程的生命周期
操作系统的线程生命周期与Java线程类似,但表述可能略有不同,大致可以分为以下几个阶段:
- 创建(Created) : 应用程序通过系统调用创建线程,分配必要的资源。
- 就绪(Ready) : 线程已创建完成,等待CPU调度。这个状态类似于Java线程的可运行状态。
- 运行(Running) : 线程被CPU选中并执行其指令。
- 阻塞(Blocked) : 等待外部事件(如I/O操作完成、信号量等)时,线程会被阻塞。操作系统会将其移出可调度队列,直到等待条件满足。
- 等待/睡眠(Wait/Sleep) : 线程主动放弃CPU执行权,进入等待或睡眠状态,可能是因为等待特定时间(类似于Java的超时等待)或外部事件(类似于Java的等待状态)。操作系统同样不会调度这类线程。
- 终止(Terminated/Dead) : 线程执行完成或因异常结束,操作系统回收其占用的资源。
Java线程的生命周期状态与操作系统线程的生命周期对应和映射关系
Java线程的生命周期状态与操作系统线程的生命周期状态之间存在一定的对应和映射关系,尽管两者在表述上有所不同,但基本原理和转换逻辑是类似的。以下是它们之间的一种对应关系概览:
-
新建(New) (Java) <---> 创建(Created) (操作系统)
- 在Java中线程刚被创建但未启动时的状态,对应于操作系统中线程刚被分配资源但尚未加入调度队列的状态。
-
可运行(Runnable) (Java) <---> 就绪(Ready)/运行(Running) (操作系统)
- Java中的可运行状态涵盖了操作系统线程的就绪(等待CPU分配)和运行(正在CPU上执行)两个状态。这是因为Java API不直接区分线程是否正被CPU执行,只关注线程是否准备好运行。
-
运行(Running) (Java) <---> 运行(Running) (操作系统)
- 当Java线程实际在CPU上执行时,对应的就是操作系统线程的运行状态。
-
阻塞(Blocked) (Java) <---> 阻塞(Blocked) (操作系统)
- 两者都表示线程因为某种资源(如锁)不可用而暂停执行,等待条件满足。
-
等待(Waiting)/超时等待(Timed Waiting) (Java) <---> 等待/睡眠(Wait/Sleep) (操作系统)
- Java的等待和超时等待状态对应操作系统中线程等待特定事件或达到指定时间后恢复执行的状态,这包括等待外部事件(如信号量)、等待一段时间后自动唤醒等。
-
终止(Terminated) (Java) <---> 终止(Terminated/Dead) (操作系统)
- 当线程执行完毕或因异常结束时,无论在Java中还是在操作系统层面,线程都进入了终止状态,意味着生命周期结束,资源被回收。