线程生命周期与状态
本教程共 100 篇 · 第 95 篇 · 更新于 2026-08-05 · 约 5 分钟阅读
95. 线程生命周期与状态
本节目标:搞清线程的六种状态和它们之间的转换关系,能看懂线程卡在哪一步。
为什么要了解线程状态
线上服务卡死不响应,通常第一步就是打线程栈(jstack),看每个线程处于什么状态。
一堆线程停在 BLOCKED,说明有锁竞争。一堆停在 WAITING,说明在等某个永远不来的通知。看懂状态,才能定位问题。
Java 用枚举 Thread.State 定义了六种状态,它们是 JVM 层面的划分,和操作系统的线程状态不完全一样。
六种状态一览
| 状态 | 含义 |
|---|---|
NEW | 已创建,还没调用 start() |
RUNNABLE | 可运行,包括正在跑和等 CPU |
BLOCKED | 等待获取 synchronized 锁 |
WAITING | 无限期等待其他线程唤醒 |
TIMED_WAITING | 限时等待,超时自动醒 |
TERMINATED | 已结束 |
下面逐个说清楚。
NEW:出生但没启动
new Thread(...) 之后、start() 之前,线程处于 NEW。
这时候它只是一个普通 Java 对象,操作系统还不知道它的存在,不会分配任何 CPU 时间。
RUNNABLE:可以跑
调用 start() 后进入 RUNNABLE。
这里有个 Java 特有的设计:它把「就绪」和「运行中」合并成了一个状态。
操作系统层面,线程有 Ready(等 CPU 调度)和 Running(正在执行)之分。但 Java 认为这个区别对程序员没有意义——你无法控制调度器什么时候切给你,所以干脆合并。
还有一点容易误解:线程等待 I/O 时也是 RUNNABLE。比如卡在 socket.read() 上等网络数据,JVM 依然报告 RUNNABLE,因为在 JVM 看来它没被 Java 层面的机制挂起。
BLOCKED:等锁
线程想进入 synchronized 代码块,但锁被别人占着,就进入 BLOCKED。
这个状态只由 synchronized 产生。用 ReentrantLock 等待锁时,状态是 WAITING 或 TIMED_WAITING,不是 BLOCKED。
锁一释放,线程会去争抢,抢到就回到 RUNNABLE。
WAITING:无限期等待
调用下面这些方法会进入 WAITING:
Object.wait()(不带超时)Thread.join()(不带超时)LockSupport.park()
关键词是无限期。线程会一直等下去,直到别的线程明确唤醒它(notify()、notifyAll()、目标线程结束、unpark())。
没人唤醒,就永远醒不过来。很多「程序假死」就是这么来的。
TIMED_WAITING:限时等待
带超时参数的版本会进入 TIMED_WAITING:
Thread.sleep(long)Object.wait(long)Thread.join(long)LockSupport.parkNanos()
时间一到自动回到 RUNNABLE,不需要别人唤醒。相比 WAITING,它安全得多,因为有兜底。
Tip设计并发逻辑时尽量用带超时的版本。多等几秒总比永久卡死强,还能在超时分支里打日志,方便排查。
TERMINATED:结束
run() 方法正常执行完,或者抛出未捕获异常退出,线程进入 TERMINATED。
一旦终止就是终点,无法回到任何其他状态。想再执行一次,只能新建线程对象。
用代码观察状态
public class StateWatch {
private static final Object LOCK = new Object();
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
try {
Thread.sleep(300); // TIMED_WAITING
synchronized (LOCK) {
LOCK.wait(); // WAITING
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}, "观察对象");
System.out.println("start 前:" + t.getState());
t.start();
Thread.sleep(50);
System.out.println("sleep 中:" + t.getState());
Thread.sleep(500);
System.out.println("wait 中:" + t.getState());
synchronized (LOCK) {
LOCK.notify(); // 唤醒
}
t.join();
System.out.println("结束后:" + t.getState());
}
}
```text
输出:
```text
start 前:NEW
sleep 中:TIMED_WAITING
wait 中:WAITING
结束后:TERMINATED
想看到 BLOCKED,需要两个线程抢同一把锁:
public class BlockedDemo {
private static final Object LOCK = new Object();
public static void main(String[] args) throws InterruptedException {
Runnable hold = () -> {
synchronized (LOCK) {
try {
Thread.sleep(2000); // 抱着锁不放
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
};
Thread t1 = new Thread(hold, "持锁者");
Thread t2 = new Thread(hold, "等锁者");
t1.start();
Thread.sleep(100);
t2.start();
Thread.sleep(100);
System.out.println("t1 状态:" + t1.getState());
System.out.println("t2 状态:" + t2.getState());
}
}
```text
输出:
```text
t1 状态:TIMED_WAITING
t2 状态:BLOCKED
t1 拿着锁在睡觉,所以是 TIMED_WAITING;t2 进不去同步块,卡在 BLOCKED。
BLOCKED 和 WAITING 的区别
初学者最容易混这两个。
BLOCKED 是被动的:我想要锁,但拿不到,只能排队。锁一空出来我就能继续,不需要谁通知我。
WAITING 是主动的:我自己调用 wait() 让出执行权,明确说「叫我起来我才走」。没人喊我就一直睡。
一个是抢不到资源,一个是主动放弃资源,性质完全不同。
中断机制
线程跑到一半想让它停下来,怎么办?
Thread 有过 stop() 方法,但早就被废弃了——它会强行终止线程,可能让共享数据停在半改不改的状态,非常危险。
Java 采用的是协作式中断:一个线程只能给另一个线程「发个信号」,对方看到信号后自己决定怎么收场。
public class InterruptDemo {
public static void main(String[] args) throws InterruptedException {
Thread worker = new Thread(() -> {
int count = 0;
while (!Thread.currentThread().isInterrupted()) {
count++;
if (count % 100_000_000 == 0) {
System.out.println("已处理 " + count);
}
}
System.out.println("收到中断信号,安全退出");
}, "可中断任务");
worker.start();
Thread.sleep(500);
worker.interrupt(); // 发信号,不是强杀
worker.join();
System.out.println("main 结束");
}
}
```java
`interrupt()` 只是把目标线程的中断标志位设成 `true`。循环里用 `isInterrupted()` 检查这个标志,主动退出。
如果线程正卡在 `sleep()`、`wait()`、`join()` 上,情况不一样:这些方法会立刻抛出 `InterruptedException`,**同时把中断标志清零**。
```java
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 手动恢复标志
return; // 然后退出
}
Warning捕获
InterruptedException后千万别只写一句e.printStackTrace()就完事。标志已经被清掉了,外层循环检查不到中断,线程会继续跑下去,中断请求就丢了。要么恢复标志,要么直接退出。
状态转换总览
把前面的内容串起来:
NEW --start()--> RUNNABLE
RUNNABLE --抢不到 synchronized 锁--> BLOCKED --拿到锁--> RUNNABLE
RUNNABLE --wait()/join()--> WAITING --notify()/目标结束--> RUNNABLE
RUNNABLE --sleep(n)/wait(n)--> TIMED_WAITING --超时--> RUNNABLE
RUNNABLE --run() 执行完--> TERMINATED
```bash
所有状态都必须经过 `RUNNABLE` 中转,不存在 `BLOCKED` 直接变 `WAITING` 这种跳转。
## 状态只能参考,不能依赖
`getState()` 拿到的是**某一瞬间的快照**。
你刚读到 `RUNNABLE`,下一纳秒线程可能已经变成 `TERMINATED` 了。所以绝不能写这种代码:
```java
// 错误示范
if (t.getState() == Thread.State.RUNNABLE) {
// 到这一行时状态可能早就变了
}
getState() 的正确用途只有一个:调试和监控。查问题时打出来看看线程卡在哪,或者做个监控面板展示线程池状况。
真正的线程协调,要靠 join()、锁、CountDownLatch 这些同步工具,它们的语义是明确且可靠的。
本节小结
- 六种状态:
NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED。 - Java 的
RUNNABLE合并了就绪和运行,等 I/O 时也算RUNNABLE。 BLOCKED只由synchronized竞争产生,是被动排队。WAITING是主动放弃执行权,必须靠别人唤醒。TIMED_WAITING有超时兜底,比WAITING更安全。TERMINATED不可逆,线程无法重启。- 停止线程用
interrupt()协作式中断,stop()已废弃。 - 捕获
InterruptedException后要恢复中断标志或直接退出。