工人的一生六个状态:NEW(招聘到岗前)→ RUNNABLE(能干活)→ TERMINATED(退休),中间随时可能去三个歇脚点——TIMED_WAITING(定时歇)、WAITING(无限等)、BLOCKED(排队抢锁)——歇够了再回 RUNNABLE 抢 CPU。
12.2 请上了第二个工人,但工人也有作息:什么时候能干活、什么时候歇着、什么时候退休——生命周期就是研究这条作息表。Java 用 Thread.State 枚举表示,t.getState() 随时可查。
一、主线三站:生、干、死
① NEW(新建):new MyThread() 完的一瞬间。工人已经招进厂(对象在内存里),但还没上工——调 run() 不算上工(12.2 假上工的账)。
② RUNNABLE(可运行):start() 之后、run() 结束之前。注意它的名字是「可运行」不是「运行中」——这个状态把「抢到 CPU 正在跑」和「排队等 CPU」合在一起了,因为抢不抢得到全看调度(12.1 抢占式随机的账)。
③ TERMINATED(死亡):run() 执行完毕。终点站,不能复生——再次 start() 直接 IllegalThreadStateException(12.2 的账,这回知道为什么了:人都退休了还怎么返聘)。
二、三个歇脚状态(重点)
RUNNABLE 的工人随时可能被叫去歇着,歇的地方有三个,区别只在「等多久、等什么」:
| 状态 | 进去的姿势 | 多久能出来 |
|---|---|---|
| TIMED_WAITING | Thread.sleep(1000)、wait(1000) 带时间 |
时间到自动醒 |
| WAITING | wait()、join() 不带时间 |
别人唤醒/干完活才醒 |
| BLOCKED | 想拿锁,锁被别的工人占着 | 等到锁(锁是什么,本章后面揭晓) |
三个状态歇完都回到 RUNNABLE 重新排队抢 CPU——不是原地接着跑。
sleep 与 join:本节两位主角
Thread.sleep(毫秒)——定时歇脚:
public class Demo19 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
for (int i = 1; i <= 3; i++) {
System.out.println("干活 " + i);
try {
Thread.sleep(500); // 歇 500 毫秒再干下一条
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
System.out.println("开工前:" + t.getState()); // NEW
t.start();
System.out.println("start 后:" + t.getState()); // RUNNABLE
Thread.sleep(100); // 主线程歇一会再看
System.out.println("干着活:" + t.getState()); // TIMED_WAITING
t.join(); // 主线程等 t 干完
System.out.println("收工后:" + t.getState()); // TERMINATED
}
}
t.join()——主线程调用后原地等 t 干完才继续。谁调 join,谁就 WAITING(无限等)。用处:想让某条线程先跑完再走后面流程,就是「插队汇合」。
Thread.sleep 声明抛 InterruptedException(受检异常),必须 try-catch 或 throws。为什么受检?因为「睡觉被打断」不是代码 bug,是外部环境(别的线程来打断你)——正好符合 5.2 的判定法。
三、一个微妙但常考的认知
RUNNABLE ≠ 正在 CPU 上跑。
- 操作系统层面线程有「就绪」「运行」之分,Java 把它们合并成一个 RUNNABLE——因为 Java 是跨平台的,不同操作系统调度细节不同,干脆打包抽象
- 所以看到
RUNNABLE,只能说明「有资格跑」,不保证「此刻在跑」
同理,getState() 看到的是调用那一瞬间的快照——多线程下状态瞬息万变,两次查询之间状态可能换了好几轮。
四、完整一生速查
new 出来 → NEW
start() → RUNNABLE
sleep(ms)/wait(ms) → TIMED_WAITING ─时间到→ 回 RUNNABLE
wait()/join() → WAITING ─被唤醒/对方完事→ 回 RUNNABLE
抢锁失败 → BLOCKED ─拿到锁→ 回 RUNNABLE
run() 跑完 → TERMINATED(终点站)
实验 1:Demo19 五连拍 跑一遍看五个状态打印——NEW、RUNNABLE、TIMED_WAITING、TERMINATED 一次看全(WAITING 和 BLOCKED 需要特殊场景,后面两节配齐)。
实验 2:sleep 睡着的线程占不占 CPU? 让线程 sleep(10000) → 开任务管理器看 CPU 占用几乎为 0。歇脚状态不占 CPU,这正是 sleep 的价值:把 CPU 让给别人。
实验 3:join 不带参数 vs 带
t.join() 主线程等到天荒地老(WAITING);t.join(1000) 最多等 1 秒(TIMED_WAITING)——同一个方法带不带毫秒参数,歇脚点都不一样。
五、常见误区
- RUNNABLE = 正在运行——它是「就绪 + 运行」的合并态,只保证有资格跑
- sleep 会释放锁——不会!sleep 睡着了抱着锁睡(这个坑本章后面「线程同步」重点清算,先记结论)
- main 也是从 NEW 走过来的——是,只是 JVM 帮你 start 了,你没亲眼看到而已
- 想让线程停就 stop()——
stop()早已废弃(暴力打死工人容易把厂子搞乱),正确姿势本章后面讲(用标志位温柔收工) - wait 和 sleep 是一回事——差远了:wait 要先持锁、会释放锁、靠别人唤醒;sleep 抱锁睡、时间到自醒(锁的细节后面揭晓)
六、自测题
NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED。
Java 把操作系统的「就绪 + 运行」合并了——它只保证有资格抢 CPU,不保证此刻在跑(跨平台抽象)。
sleep(1000) → TIMED_WAITING,时间到自动醒;join() → WAITING,等目标线程 run 结束才回。
TERMINATED 是终点站,线程对象的一条命只能 start 一次——复生在 JVM 层面就不允许。
要点:① new 后立刻 getState → NEW;② start 后立刻 → RUNNABLE;③ run 里 sleep,主线程稍等再查 → TIMED_WAITING;④ t.join() 后 → TERMINATED;⑤ 加分:说出 sleep 必须 try-catch 是因为 InterruptedException 受检、属于外部环境异常。全对毕业。
下一步学什么
- ☑ 12.4 线程操作的相关方法 ✅ —— 点此复习:sleep/join/守护线程全部领工牌
相关笔记
- 第十二章 多线程/12.1 进程与线程:抢占式调度随机的出处
- 第十二章 多线程/12.2 线程的创建:start 一次的账、假上工的账
- 5.2 运行时异常与编译时异常:InterruptedException 的受检判定
- 第四章 面向对象(下)/4.7 函数式编程:Demo19 的 Lambda 任务
- 5.4 抛出异常:谁调用谁处理的传递链