生命周期
1 整体生命周期状态图
经典图:清晰区分 NEW → RUNNABLE(就绪+运行)→ BLOCKED / WAITING / TIMED_WAITING → TERMINATED,并标注了所有进入方式。
五状态经典图:新建 → 就绪 → 运行 → 阻塞 → 死亡。
超级详细版(带文字说明):把就绪细分为“等待CPU”和“运行中”,阻塞分为各种场景,非常适合深入理解。
stateDiagram-v2
[*] --> NEW : new Thread()
NEW --> RUNNABLE : start()
RUNNABLE --> BLOCKED : 等待 synchronized 锁
BLOCKED --> RUNNABLE : 获取到锁
RUNNABLE --> WAITING : wait() / join()
RUNNABLE --> TIMED_WAITING : sleep() / wait(time)
WAITING --> RUNNABLE : notify() / interrupt()
TIMED_WAITING --> RUNNABLE : 时间到 / interrupt()
RUNNABLE --> TERMINATED : run() 执行完毕
BLOCKED --> TERMINATED : 极少见,通常先变 RUNNABLE
WAITING --> TERMINATED : 极少见
TIMED_WAITING --> TERMINATED : 极少见
线程生命周期核心规则:
状态单向不可逆
一旦线程调用start()进入 RUNNABLE,就永远回不到 NEW;一旦到达 TERMINATED,就再也无法进入任何其他状态(终态,不可复活)。同一个线程对象只能 start() 一次
第二次调用start()会直接抛出 IllegalThreadStateException(一生只能启动一次)。RUNNABLE 是“万能中转站”
几乎所有状态转换都要经过 RUNNABLE(包括就绪+运行两种OS层面状态,JVM不细分);NEW → RUNNABLE → 各种阻塞/等待 → RUNNABLE → TERMINATED 是最典型路径。阻塞与等待的唤醒依赖外部事件
- BLOCKED(synchronized锁竞争)→ 自动靠获得锁转为RUNNABLE
- WAITING → 必须被 notify/notifyAll、unpark 或 被join的线程结束 唤醒
- TIMED_WAITING → 除上述外,还可以靠时间到达自动唤醒
程序员无法“强行把线程从等待中拽出来”,必须靠这些触发条件。禁止使用 stop()、suspend()、resume()
这几个方法早在 JDK 1.2 就标记为Deprecated,极不安全:
- stop() 会直接杀死线程,可能导致资源不释放、对象处于不一致状态
- suspend() 会让线程永久挂起而不释放锁,极易死锁
现代推荐做法:用 interrupt() + 主动检查中断标志(isInterrupted() / Thread.interrupted())来协作式终止线程。
interrupt() 只是“礼貌请求”中断,不是强制杀死
- 仅对处于 sleep()、wait()、join()、park() 等可中断方法的线程会抛出 InterruptedException
- 对普通运行代码无直接效果,必须由线程自己检查中断状态并主动退出 (这是“优雅关闭线程”的核心原则)
run() 执行结束或未捕获异常 = 死亡
线程的 TERMINATED 状态只能通过 run() 方法正常返回 或 抛出未捕获异常两种方式到达,没有其他途径。
2 各状态详细说明
总结:
新建 →
start()→ 就绪 → 获取CPU → 运行
运行 →
sleep/wait/join/park/synchronized拿不到锁→ 阻塞yield()/时间片结束→ 就绪run()结束/异常→ 终止阻塞 →
时间到/notify/unpark/锁释放→ 就绪
(1)新建状态(New)
-
特征:线程对象已被
new创建,但还未调用start()方法。同一个线程对象不能多次调用start ()方法,否则会抛出IllegalThreadStateException异常。 -
此时线程:还没有被 JVM 分配资源(栈空间、程序计数器等),本质上只是一个普通 Java 对象。
-
能做什么:只能调用
start(),不能调用run()(直接调用run()只是普通方法,不会启动新线程)。 -
代码示例:
Thread t = new Thread(() -> {
System.out.println("我正在运行");
}); // ← 此时处于 New 状态
状态流转:new Thread() → 新建 → t.start() → 就绪
(2)就绪状态(Runnable / Ready)
- 特征:已调用
start(),线程已经准备好运行,等待操作系统分配 CPU 时间片。 - 注意:Java 官方
Thread.State中把就绪 + 运行合并为一个RUNNABLE状态,但国内通常把它们拆开讲解。 - 此时线程:在就绪队列中排队,具备运行的一切条件,只差 CPU。
- 代码示例:
t.start(); // ← 调用后立即进入就绪状态
状态流转:
- 就绪 → 获得 CPU → 运行
- 运行中被抢占 / yield() / 时间片用完 → 回到 就绪
(3)运行状态(Running)
- 特征:线程已获得 CPU 时间片,正在真正执行
run()方法中的代码。 - 此时线程:是 CPU 的“宠儿”,正在跑业务逻辑。
- 可能被打断的情况:
- 时间片用完(最常见)
- 调用
Thread.yield()(主动让出) - 被更高优先级线程抢占
- 执行到阻塞操作(如 sleep、wait、synchronized 拿不到锁)
状态流转:
- 运行 → 执行完毕或异常 → 终止
- 运行 → 遇到阻塞操作 → 阻塞
(4)阻塞状态(Blocked / Waiting / Timed Waiting)
这是面试最爱深挖的部分。阻塞不是一个单一状态,而是三大类:
| 子状态 | 英文 | 触发方式(最常见) | 唤醒方式 | 特点 |
|---|---|---|---|---|
| Blocked | BLOCKED | 竞争 synchronized 锁失败 | 其他线程释放锁 | 等待锁(monitor) |
| Waiting | WAITING | obj.wait()、thread.join()、LockSupport.park() | notify()/notifyAll()、unpark()、join线程结束 | 无限期等待 |
| Timed Waiting | TIMED_WAITING | Thread.sleep()、wait(timeout)、join(timeout)、parkNanos() | 时间到 或 被提前唤醒 | 有超时时间 |
代码示例(三大阻塞):
// 1. BLOCKED(锁阻塞)
synchronized (obj) {
// 如果其他线程已经拿到锁,这里就会进入 BLOCKED
}
// 2. WAITING(无限等待)
synchronized (obj) {
obj.wait(); // 必须在 synchronized 中调用
}
// 3. TIMED_WAITING(限时等待)
Thread.sleep(2000); // 最常见
obj.wait(2000);
t.join(5000);
重要区别记忆口诀:
- Blocked = 等锁(synchronized)
- Waiting = 主动“喊暂停”(wait/park/join),需要别人叫醒
- Timed Waiting = “喊暂停但带闹钟”(sleep/wait+timeout)
状态流转:所有阻塞 → 条件满足 → 回到 就绪
(5)终止状态(Terminated)
- 特征:线程
run()方法正常结束、抛出未捕获异常、或被stop()(已废弃,不推荐)。 - 此时线程:彻底死亡,资源被回收,不能再
start()(会抛IllegalThreadStateException)。 - 代码示例:
// run() 执行完自动进入 Terminated
// 或线程内抛出 RuntimeException 未捕获
状态流转:不可逆!终止后无法回到任何其他状态。
3 代码演示
package Thread_Lifecycle;
/**
* 线程生命周期状态演示类
* 展示Java线程的6种状态:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED
*/
public class ThreadStateDemo {
// 定义一个共享对象锁,用于演示线程同步和阻塞状态
private static final Object lock = new Object();
/**
* 主方法 - 演示各种线程状态
* @param args 命令行参数
* @throws InterruptedException 当线程被中断时抛出
*/
public static void main(String[] args) throws InterruptedException {
// 1. NEW 状态演示
// 创建线程对象但未调用start()方法,此时线程处于NEW状态
Thread t1 = new Thread(() -> {
// 线程执行体 - 实际工作代码
System.out.println("t1 进入运行状态...");
try {
// 此时线程进入TIMED_WAITING状态
Thread.sleep(200);
} catch (InterruptedException e) {
// 处理中断异常情况
System.out.println("t1 被中断");
}
// 线程执行完毕,即将进入TERMINATED状态
System.out.println("t1 执行结束 → TERMINATED");
}, "t1"); // 给线程命名,便于识别和调试
// 获取并打印线程创建后的初始状态(应该是NEW)
System.out.println("t1 创建后状态: " + t1.getState());
// 2. RUNNABLE状态演示
// 调用start()方法启动线程,线程进入RUNNABLE状态(就绪态)
// JVM调度器会很快将其分配CPU时间片开始执行
t1.start();
// 主线程休眠100毫秒,给t1线程时间开始执行
Thread.sleep(100);
// 此时t1应该已经在执行sleep()方法,处于TIMED_WAITING状态
System.out.println("t1 start 后状态: " + t1.getState());
// 3. BLOCKED状态演示(锁竞争场景)
// 创建第一个线程t2,它会获取lock对象的监视器锁
Thread t2 = new Thread(() -> {
// 使用synchronized关键字获取lock对象的内置锁
synchronized (lock) {
System.out.println("t2 拿到锁,正在执行...");
try {
// 持有锁的情况下休眠0.3秒,期间其他线程无法获取该锁
Thread.sleep(300);
} catch (InterruptedException ignored) {
// 忽略中断异常
}
}
// synchronized块结束时自动释放锁
}, "t2");
// 创建第二个线程t3,它也会尝试获取同一个lock对象的锁
Thread t3 = new Thread(() -> {
// 当t2持有锁时,t3执行到这里会被阻塞
// 因为无法获取已被t2占用的锁,进入BLOCKED状态
synchronized (lock) {
System.out.println("t3 拿到锁");
}
}, "t3");
// 启动t2线程,让它先获取锁
t2.start();
// 等待200毫秒确保t2已经获取到锁
Thread.sleep(200);
// 启动t3线程,此时t3会因为锁被占用而进入BLOCKED状态
t3.start();
// 等待300毫秒让t3充分体验BLOCKED状态
Thread.sleep(300);
// 打印t3的状态,应该显示为BLOCKED
System.out.println("t3 状态(应为 BLOCKED): " + t3.getState());
// 4. WAITING状态演示(使用Object.wait()方法)
// 创建t4线程用于演示无限期等待状态
Thread t4 = new Thread(() -> {
// 获取lock对象的锁
synchronized (lock) {
try {
System.out.println("t4 进入 wait → WAITING");
// 调用wait()方法释放锁并进入无限期等待状态
// 直到其他线程调用notify()/notifyAll()才会唤醒
lock.wait();
} catch (InterruptedException e) {
// 处理可能的中断异常
e.printStackTrace();
}
}
}, "t4");
// 启动t4线程
t4.start();
// 等待500毫秒确保t4进入WAITING状态
Thread.sleep(500);
// 打印t4状态,应该显示为WAITING
System.out.println("t4 状态(应为 WAITING): " + t4.getState());
// 唤醒处于WAITING状态的t4线程
// 需要先获取相同的锁对象才能调用notify()
synchronized (lock) {
// 唤醒在lock对象上等待的单个线程(t4)
lock.notify();
}
// 5. TIMED_WAITING状态演示(使用带超时的join方法)
// 创建t5线程用于演示定时等待
Thread t5 = new Thread(() -> {
try {
// t5线程简单地休眠0.5秒
Thread.sleep(500);
} catch (InterruptedException ignored) {
// 忽略中断异常
}
}, "t5");
// 启动t5线程
t5.start();
// 获取当前主线程的引用
Thread main = Thread.currentThread();
// 创建一个辅助线程来演示主线程的TIMED_WAITING状态
new Thread(() -> {
try {
// 调用t5.join(2000)使当前线程(辅助线程)等待t5线程最多0.2秒
// 如果2秒内t5没有结束,则继续执行
// 调用线程在此期间进入TIMED_WAITING状态
t5.join(500);
} catch (InterruptedException e) {
// 处理中断异常
e.printStackTrace();
}
}).start();
// 等待500毫秒确保辅助线程已经开始等待
Thread.sleep(500);
// 打印主线程状态,应该显示为TIMED_WAITING
System.out.println("主线程在 join(2000) 时状态: " + main.getState());
// 等待所有演示线程完成执行
// join()方法使当前线程(main线程)等待指定线程执行完毕
t1.join();
t2.join();
t3.join();
t4.join();
t5.join();
// 打印t1线程的最终状态,应该是TERMINATED
System.out.println("t1 最终状态: " + t1.getState());
}
}
4 wait() 和 sleep() 的 8 大区别(面试必问)
口诀总结:“wait会放锁睡大觉,别人叫醒才起床;sleep抱锁睡小觉,时间一到自己醒。”
| 序号 | 比较项 | wait() | sleep() | 记忆口诀 |
|---|---|---|---|---|
| 1 | 所属类 | Object | Thread | wait是对象的,sleep是线程的 |
| 2 | 使用环境 | 必须在 synchronized 块/方法内 | 无需 synchronized | wait必须“有锁” |
| 3 | 是否释放锁 | 释放当前持有的锁 | 不释放锁 | wait会放锁,sleep死抓着 |
| 4 | 唤醒方式 | notify/notifyAll 或 interrupt | 时间到自动醒,或 interrupt | wait靠别人,sleep靠闹钟 |
| 5 | 是否可超时 | 有重载 wait(long timeout) | 有 sleep(long millis) | 两者都可带时间 |
| 6 | 中断响应 | 抛 InterruptedException 并清除中断标志 | 抛 InterruptedException 并清除中断标志 | 行为一致 |
| 7 | 所属状态(最典型) | WAITING 或 TIMED_WAITING | TIMED_WAITING | sleep一定是限时等待 |
| 8 | 假唤醒(spurious wakeup) | 可能发生,必须放在 while 循环中判断条件 | 不存在假唤醒 | wait要防“骗醒” |
5 Thread.State 枚举源码分析
理解线程状态流转对于排查死锁和性能问题至关重要。Java 线程主要有以下 6 种状态(基于 Thread.State 枚举):
- NEW (新建):创建了线程对象,但未调用
start()。 - RUNNABLE (可运行):调用了
start(),正在 JVM 中等待 CPU 时间片,或者正在运行中。 - BLOCKED (阻塞):等待获取监视器锁(synchronized 锁)。
- WAITING (无限等待):调用
wait(),join(),LockSupport.park(),需被其他线程唤醒。 - TIMED_WAITING (计时等待):调用
sleep(time),wait(time),时间到了自动唤醒。 - TERMINATED (终止):线程执行结束。
public class Thread implements Runnable {
public enum State {
/**线程尚未启动。还未调用 start()。*/
NEW,
/**线程在 JVM 中执行中(包括就绪 + 真正运行 + OS 调度相关等待)。
* 这是最宽泛的状态。*/
RUNNABLE,
/** 线程在等待 monitor 锁(synchronized 块/方法)。
* 典型场景:竞争 synchronized 锁失败。*/
BLOCKED,
/** 线程在无限期等待另一个线程执行特定动作。
* 典型:wait()、join()、LockSupport.park()*/
WAITING,
/** 线程在有限时间内等待另一个线程执行特定动作。
* 典型:sleep()、wait(timeout)、join(timeout)、parkNanos、parkUntil*/
TIMED_WAITING,
/**线程已终止(run()正常结束或异常结束)。
* 不可再 start()。*/
TERMINATED;
}
}
6 Java线程状态与操作系统内核线程的对应关系
| Java 线程状态 | 操作系统层面(典型对应) | 说明 |
|---|---|---|
| NEW | 无(尚未创建内核线程) | Java 层只是对象,start() 后才真正创建 pthread / kernel thread |
| RUNNABLE | Ready / Running | 就绪队列中等待调度 或 正在 CPU 上执行 |
| BLOCKED | Waiting (for mutex / monitor) | 等待互斥锁(synchronized 底层是 monitorenter) |
| WAITING | Waiting / Suspended / Parked | 等待条件变量(wait/notify 底层是条件变量)、futex wait、park 等 |
| TIMED_WAITING | Waiting with timeout | 同上,但设置了超时(sleep、wait(timeout)、pthread_cond_timedwait 等) |
| TERMINATED | Zombie → Dead | 线程结束,等待被 join 或系统回收资源 |
总结对应规律:
-
Java 故意简化了状态(RUNNABLE 合并就绪+运行),隐藏了操作系统细节
-
BLOCKED 是最“重”的阻塞(与锁相关),其他都是“轻量级”等待
-
现代 JVM(HotSpot)底层大量使用 park/unpark(基于 futex / semaphore 等),而不是传统条件变量
Grok给出:
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
import java.util.concurrent.locks.ReentrantLock;
public class ThreadStateDemo {
private static final ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) throws Exception {
// 1. NEW 状态(创建但未start)
Thread tNew = new Thread(() -> {}, "t-new");
printThreadInfo(tNew, "刚new出来");
// 2. RUNNABLE(正在计算或ready)
Thread tRunnable = new Thread(() -> {
long start = System.currentTimeMillis();
while (true) {
if (System.currentTimeMillis() - start > 20_000) break;
Math.sin(Math.random()); // 占用CPU
}
}, "t-runnable");
tRunnable.start();
Thread.sleep(800); // 让它跑起来
printThreadInfo(tRunnable, "RUNNABLE - 忙循环");
// 3. BLOCKED(synchronized争锁)
Thread tBlocked = new Thread(() -> {
synchronized (ThreadStateDemo.class) {
LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(30));
}
}, "t-blocked");
// 先让主线程持有锁
synchronized (ThreadStateDemo.class) {
tBlocked.start();
Thread.sleep(800);
printThreadInfo(tBlocked, "BLOCKED - 争synchronized锁");
}
// 4. WAITING(Object.wait() 无超时)
Thread tWaiting = new Thread(() -> {
synchronized (ThreadStateDemo.class) {
try {
ThreadStateDemo.class.wait(); // 会一直等
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}, "t-waiting");
tWaiting.start();
Thread.sleep(800);
printThreadInfo(tWaiting, "WAITING - Object.wait()");
// 5. TIMED_WAITING(几种常见形式)
Thread tTimed = new Thread(() -> {
try {
Thread.sleep(15000); // 最简单的一种
// 或者: LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(30));
// 或者: synchronized后 wait(15000)
} catch (Exception e) {
Thread.currentThread().interrupt();
}
}, "t-timed-waiting");
tTimed.start();
Thread.sleep(800);
printThreadInfo(tTimed, "TIMED_WAITING - Thread.sleep()");
// 6. 让程序不要马上退出
System.out.println("\n所有演示线程已启动。pid = " + ProcessHandle.current().pid());
System.out.println("请在新终端运行以下命令观察(大约10秒内):");
System.out.println(" jstack " + ProcessHandle.current().pid());
System.out.println(" top -H -p " + ProcessHandle.current().pid());
System.out.println(" ps -eLo pid,lwp,stat,comm | grep t-");
System.out.println(" cat /proc/" + ProcessHandle.current().pid() + "/task/[LWP]/stat # 看第3列状态字母");
System.out.println("\n按回车退出...");
System.in.read();
}
private static void printThreadInfo(Thread t, String msg) {
System.out.printf("%-25s : %-10s (java state)%n", msg, t.getState());
}
}