# 2026-08-10 线程操作与生命周期练习(课堂练习用) > **说明**:练习今日新学知识点——**线程操作方法**(`Thread.sleep()` 线程休眠、`getPriority()` / `setPriority()` 线程优先级、`join()` / `join(long)` 等待线程结束、`interrupt()` / `isInterrupted()` 中断标志机制、`yield()` 主动让出 CPU、`isAlive()` 判断存活)、**守护线程**(`setDaemon()` / `isDaemon()`、JVM 退出机制、守护线程特点)、**线程生命周期**(`Thread.State` 枚举 6 种状态:NEW / RUNNABLE / BLOCKED / WAITING / TIMED_WAITING / TERMINATED、`getState()` 观察状态)、**Object 类线程方法**(`wait()` / `notify()` / `notifyAll()` 等待唤醒)。 --- ## 练习一:线程生命周期观察 —— getState() 打印 6 种状态 **难度**:⭐⭐⭐ **知识点**:`Thread.State` 枚举、`getState()`、线程 6 种状态(NEW / RUNNABLE / TIMED_WAITING / TERMINATED)、`join()`、`sleep()` **场景描述**:课堂学了**线程生命周期**:Java 中线程有 6 种状态(基于 `Thread.State` 枚举)——新建 NEW、可运行 RUNNABLE、阻塞 BLOCKED、等待 WAITING、计时等待 TIMED_WAITING、终止 TERMINATED。本例用 `getState()` 方法在**不同阶段**打印线程状态,直观观察线程从"出生"到"死亡"的状态变化(可观察到的有 NEW / RUNNABLE / TIMED_WAITING / TERMINATED)。 ### 案例需求 使用 `getState()` 方法观察一个线程在"创建→启动→休眠→结束"各阶段的状态。 ### 实现步骤 1. 创建一个线程对象(任务:循环打印并 sleep 500ms) 2. `start()` 之前打印状态 → 预期 **NEW** 3. `start()` 之后立即打印状态 → 预期 **RUNNABLE**(或运行中) 4. 主线程 sleep 200ms 后打印子线程状态 → 预期 **TIMED_WAITING**(子线程正在 sleep) 5. `join()` 等待子线程结束后打印状态 → 预期 **TERMINATED** ### 代码框架 ```java public class LifecycleTest { public static void main(String[] args) throws InterruptedException { // ========== 1、创建线程对象 ========== Thread t = new Thread(() -> { for (int i = 0; i < 5; i++) { try { Thread.sleep(500); // sleep 中 → TIMED_WAITING 计时等待 } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("子线程工作中---->" + i); } }, "worker"); // ========== 2、start() 之前:NEW 状态 ========== // TODO 1: System.out.println("start() 之前状态:" + t.getState()); // 预期 NEW // ========== 3、start() 之后:RUNNABLE 状态 ========== // TODO 2: t.start(); 并打印状态 // 预期 RUNNABLE // ========== 4、主线程 sleep,观察子线程 sleep 期间的状态 ========== // TODO 3: Thread.sleep(200); 后打印 t.getState() // 预期 TIMED_WAITING // ========== 5、join 等待子线程结束,打印 TERMINATED ========== // TODO 4: t.join(); 后打印 t.getState() // 预期 TERMINATED } } ``` **输出示例**: ``` start() 之前状态:NEW start() 之后状态:RUNNABLE 子线程工作中---->0 子线程 sleep 期间状态:TIMED_WAITING 子线程工作中---->1 ... 子线程结束后状态:TERMINATED ``` **选做加分**: - 在主线程中创建 3 个线程,分别打印它们的 `getState()`,对比多个线程并发时的状态 - 在线程 run() 中调用 `wait()`(需 synchronized 同步块),在主线程打印它的状态观察 **WAITING** - 用两个线程竞争一个 `synchronized` 锁,观察未抢到锁的线程状态是否为 **BLOCKED** **思考题**: 1. 为什么 `getState()` 在 `start()` 之后立即调用,返回的多半是 RUNNABLE 而不是 RUNNING?(提示:Java 把"运行"和"就绪"合并成一个 RUNNABLE 状态,无法精确区分) 2. `sleep()` 期间线程处于什么状态?(提示:TIMED_WAITING 计时等待,超时自动恢复) 3. `join()` 让调用线程进入什么状态?(提示:WAITING 无限等待;join(timeout) 进入 TIMED_WAITING) 4. 线程为什么 `TERMINATED` 之后就"回不去"了?(提示:TERMINATED 是终点站,run() 结束不可逆) --- ## 练习二:线程优先级 + sleep —— 运动员赛跑并发演示 **难度**:⭐⭐⭐ **知识点**:`getPriority()` / `setPriority()`(默认 5、范围 1~10)、`Thread.MAX_PRIORITY` / `NORM_PRIORITY` / `MIN_PRIORITY`、`Thread.sleep()`、Lambda 创建 Runnable、抢占式调度模型、多线程并发随机性 **场景描述**:课堂学了**线程优先级**:Java 采用**抢占式调度模型**,**优先级高的线程**获得 CPU 时间片更多;线程默认优先级是 5,范围 1~10。本例用 3 个"运动员"线程赛跑,分别设置最高 / 普通 / 最低优先级,配合 `sleep()` 模拟跑步,观察优先级对获得 CPU 机会的影响,以及多线程执行的随机性。 ### 案例需求 创建 3 个运动员线程(优先级分别为 10 / 5 / 1),每个跑 10 圈(每圈 sleep 100ms),观察到达终点的先后顺序。 ### 实现步骤 1. 用 Lambda 创建 Runnable 任务(获取当前线程名 + 优先级,循环打印圈数) 2. 创建 3 个 Thread 对象(运动员1 / 2 / 3) 3. 打印默认优先级(验证都是 5) 4. 分别设置优先级:`MAX_PRIORITY`(10) / `NORM_PRIORITY`(5) / `MIN_PRIORITY`(1) 5. 修改后再打印一次优先级,验证 setPriority 生效 6. `start()` 启动 3 个线程,观察输出交错 ### 代码框架 ```java public class RaceTest { public static void main(String[] args) { // ========== 1、Lambda 创建 Runnable 任务 ========== Runnable runner = () -> { // TODO 1: 获取当前线程名和优先级 // 用 Thread.currentThread().getName() / getPriority() for (int i = 1; i <= 10; i++) { try { Thread.sleep(100); // 模拟跑步速度 } catch (InterruptedException e) { throw new RuntimeException(e); } // TODO 2: 打印 "线程名(优先级X)跑完第 i 圈" } // TODO 3: 打印 "线程名 到达终点!" }; // ========== 2、创建 3 个运动员线程 ========== // TODO 4: new Thread(runner, "运动员1") / "运动员2" / "运动员3" // ========== 3、打印默认优先级(都是 5)========== // TODO 5: System.out.println(t1.getName() + " 默认优先级:" + t1.getPriority()); // ========== 4、设置不同优先级 ========== // TODO 6: t1.setPriority(Thread.MAX_PRIORITY); // 10 // t2.setPriority(Thread.NORM_PRIORITY); // 5 // t3.setPriority(Thread.MIN_PRIORITY); // 1 // ========== 5、再次打印优先级 ========== // TODO 7: 打印三个线程修改后的优先级,验证 10 / 5 / 1 // ========== 6、启动 3 个线程 ========== // TODO 8: t1.start(); t2.start(); t3.start(); } } ``` **输出示例**: ``` 运动员1 默认优先级:5 运动员2 默认优先级:5 运动员3 默认优先级:5 运动员1 优先级改为:10 运动员2 优先级改为:5 运动员3 优先级改为:1 运动员1(优先级10)跑完第 1 圈 运动员3(优先级1)跑完第 1 圈 运动员2(优先级5)跑完第 1 圈 ... ``` **选做加分**: - 把 `setPriority(11)` 传入试试,观察抛出的 `IllegalArgumentException` - 去掉 `Thread.sleep(100)` 再运行,观察输出速度与交错变化——sleep 放慢速度便于观察 - 循环运行多次,统计"到达终点"的先后,观察高优先级线程(10)是否**更可能**先到 **思考题**: 1. 线程默认优先级是多少?取值范围是多少?(提示:默认 5,范围 1~10) 2. 高优先级线程一定先执行完吗?(提示:不一定!优先级只是提高获得 CPU 的"概率",抢占式调度 + 随机性决定实际顺序) 3. 为什么 Java 要用抢占式调度模型?(提示:优先让优先级高的线程使用 CPU,优先级高的线程获得的时间片更多) 4. `Thread.sleep(100)` 在这个例子中起什么作用?(提示:主动让出时间片、放慢速度,让并发输出交错更明显) --- ## 练习三:守护线程 —— 后台心跳监控(JVM 退出机制验证) **难度**:⭐⭐⭐⭐ **知识点**:`setDaemon(boolean)`、`isDaemon()`、用户线程 vs 守护线程、JVM 退出条件(所有用户线程结束才退出)、守护线程特点(start() 前设置、被强杀时 finally 不保证执行) **场景描述**:课堂学了**守护线程**:它是服务型线程,专门为用户线程提供后台支持;**只要还有一个用户线程存活,JVM 就不会退出**,而**只剩守护线程时 JVM 直接退出**,守护线程被强制终止。本例实现一个"主任务(用户线程)+ 心跳监控(守护线程)"的场景,验证 main 结束后 JVM 是否退出、守护线程是否被强制终止。 ### 案例需求 创建主任务线程(用户线程,执行 3 秒)和心跳监控线程(守护线程,无限循环报告心跳)。观察 main 结束后:用户线程还在跑时 JVM 是否退出?守护线程是否还在打印? ### 实现步骤 1. 创建用户线程(主任务):循环 3 次,每次 sleep 1 秒打印"主任务执行中...第 N 秒" 2. 创建守护线程(心跳监控):无限循环,每 300ms 打印一次心跳次数 3. 在 `start()` **之前**调用 `heart.setDaemon(true)` 4. 打印两个线程的 `isDaemon()`,验证守护属性 5. `start()` 启动两个线程,观察 main 结束后的输出 ### 代码框架 ```java public class DaemonTest { public static void main(String[] args) { // ========== 1、用户线程(主任务):执行 3 秒 ========== Thread user = new Thread(() -> { for (int i = 1; i <= 3; i++) { try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } // TODO 1: 打印 "主任务执行中...第 i 秒" } // TODO 2: 打印 "主任务执行完毕!" }, "主任务"); // ========== 2、守护线程(心跳监控):无限循环 ========== Thread heart = new Thread(() -> { int count = 0; while (true) { try { Thread.sleep(300); } catch (InterruptedException e) { throw new RuntimeException(e); } // TODO 3: 打印 "心跳检测中...第 (++count) 次" } }, "心跳监控"); // ========== 3、设置守护线程(必须在 start 之前!)========== // TODO 4: heart.setDaemon(true); // ========== 4、验证 isDaemon ========== // TODO 5: 打印 heart.isDaemon()(预期 true)和 user.isDaemon()(预期 false) // ========== 5、启动两个线程 ========== // TODO 6: user.start(); heart.start(); // main 结束后:只剩守护线程 → JVM 退出 → 心跳线程被强制终止 } } ``` **输出示例**: ``` 心跳监控是否是守护线程:true 主任务是否是守护线程:false 主任务执行中...第 1 秒 心跳检测中...第 1 次 心跳检测中...第 2 次 心跳检测中...第 3 次 主任务执行中...第 2 秒 ... 主任务执行完毕! ← main 结束,JVM 退出,心跳检测不再打印 ``` **选做加分**: - 把 `heart.setDaemon(true)` 移到 `heart.start()` **之后**,观察抛 `IllegalThreadStateException` - 把心跳线程改成用户线程(不 setDaemon),运行后观察 main 结束后 JVM **是否退出**——心跳会一直打印停不下来 - 在心跳线程 run() 的 try-finally 中打印"资源清理",观察守护线程被强杀时 finally 是否执行 **思考题**: 1. JVM 什么时候退出?(提示:所有用户线程(普通线程)都结束时才退出;main 主线程也是用户线程) 2. 为什么 main 结束了,心跳监控(守护线程)还会被强制终止?(提示:只剩守护线程时 JVM 直接结束,守护线程被强杀) 3. `setDaemon(true)` 为什么必须在 `start()` 之前调用?(提示:线程启动后守护属性不可修改,否则抛 IllegalThreadStateException) 4. 守护线程适合做什么任务?能不能用来做资源清理?(提示:适合心跳检测、后台日志、监控统计等;被强杀时 finally 不保证执行,不能用于资源清理) --- ## 练习四:join + interrupt + yield —— 主线程协作等待 **难度**:⭐⭐⭐⭐ **知识点**:`join()` / `join(long)` 等待线程结束、`interrupt()` / `isInterrupted()` 中断标志机制、`InterruptedException`、catch 后重设中断标志、`yield()` 主动让出 CPU **场景描述**:课堂学了线程操作方法:`join()` 让当前线程等待目标线程执行完毕;`interrupt()` 设置中断标志(不杀线程),线程在 sleep/wait/join 中被中断会抛 `InterruptedException` 并清除标志,catch 后通常重设标志;`yield()` 主动让出 CPU 时间片。本例综合运用这三种方法:① 主线程 `join()` 等子线程完成;② 中断一个正在 sleep 的线程;③ 子线程用 `yield()` 让出 CPU。 ### 案例需求 演示 join / interrupt / yield 三种线程操作方法的协作场景。 ### 实现步骤 1. **join 演示**:创建 worker-1 线程(循环 5 次:yield 让出 CPU → 打印 → sleep 200ms),主线程 `worker.join()` 等它结束 2. **interrupt 演示**:创建 worker-2 线程(sleep 5000ms 模拟长任务),主线程 sleep 500ms 后 `interrupt()` 中断它,观察 catch 中抛异常、重设标志 3. 两个子线程结束后,主线程打印"主线程结束" ### 代码框架 ```java public class CooperationTest { public static void main(String[] args) throws InterruptedException { // ========== 1、join + yield 演示 ========== Thread worker = new Thread(() -> { for (int i = 0; i < 5; i++) { // TODO 1: Thread.yield(); 主动让出 CPU 时间片 // TODO 2: 打印 "worker-1 工作中---> i" try { Thread.sleep(200); } catch (InterruptedException e) { // TODO 3: Thread.currentThread().interrupt(); 重设中断标志 break; } } }, "worker-1"); worker.start(); // TODO 4: worker.join(); 主线程等待 worker-1 结束 // TODO 5: 打印 "worker-1 已结束,主线程继续..." // ========== 2、interrupt 中断演示 ========== Thread sleeper = new Thread(() -> { try { Thread.sleep(5000); // 长任务:睡 5 秒 System.out.println("任务完成"); // 被中断就不会执行到这 } catch (InterruptedException e) { // TODO 6: Thread.currentThread().interrupt(); 重设中断标志 // TODO 7: 打印 "sleep 中被中断,isInterrupted()= 当前值" } }, "worker-2"); sleeper.start(); Thread.sleep(500); // 让 sleeper 先进入 sleep 状态 // TODO 8: sleeper.interrupt(); 向 worker-2 发送中断请求 sleeper.join(); // 主线程等 worker-2 处理完中断 // TODO 9: 打印 "主线程结束" } } ``` **输出示例**: ``` worker-1 工作中---> 0 worker-1 工作中---> 1 ... worker-1 已结束,主线程继续... sleep 中被中断,isInterrupted()= true 主线程结束 ``` **选做加分**: - 把 `worker.join()` 改成 `worker.join(500)`,观察主线程最多等 500ms 就继续(不等 worker-1 跑完 5 轮) - 去掉 catch 里的 `Thread.currentThread().interrupt()`,观察外层 `isInterrupted()` 的值变化——体会"InterruptedException 抛出时标志被清除" - 在线程 run() 中写 `while (true)` + `if (Thread.currentThread().isInterrupted()) break;`,用 `interrupt()` 优雅地结束无限循环线程 **思考题**: 1. `join()` 和 `join(long)` 的区别是什么?(提示:join() 无限等待目标线程结束;join(millis) 最多等待指定毫秒,超过就不等) 2. `interrupt()` 会杀死线程吗?(提示:不会!只是设置中断标志,请求线程自行响应,是否退出由线程自己决定) 3. 为什么 `InterruptedException` 抛出的同时会**清除中断标志**?(提示:阻塞方法被中断时先清标志再抛异常;catch 后重新 interrupt() 是为了让外层 isInterrupted() 能感知到中断) 4. `yield()` 和 `sleep(ms)` 有什么区别?(提示:yield 只是让出一次 CPU 回到 RUNNABLE,可能马上又被调度;sleep 是限时休眠进入 TIMED_WAITING) --- # 参考答案要点 ## 练习一:LifecycleTest(getState() 观察线程状态) ```java // 1、start() 之前:NEW(TODO 1) System.out.println("start() 之前状态:" + t.getState()); // NEW // 2、start() 之后:RUNNABLE(TODO 2) t.start(); System.out.println("start() 之后状态:" + t.getState()); // RUNNABLE // 3、主线程 sleep 200ms 后:子线程处于 TIMED_WAITING(TODO 3) Thread.sleep(200); System.out.println("子线程 sleep 期间状态:" + t.getState()); // TIMED_WAITING // 4、join 等待子线程结束:TERMINATED(TODO 4) t.join(); System.out.println("子线程结束后状态:" + t.getState()); // TERMINATED ``` ## 练习二:RaceTest(线程优先级赛跑) ```java // 1、Lambda 创建 Runnable 任务(TODO 1~3) Runnable runner = () -> { String name = Thread.currentThread().getName(); int priority = Thread.currentThread().getPriority(); for (int i = 1; i <= 10; i++) { try { Thread.sleep(100); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println(name + "(优先级" + priority + ")跑完第 " + i + " 圈"); } System.out.println(name + " 到达终点!"); }; // 2、创建 3 个运动员线程(TODO 4) Thread t1 = new Thread(runner, "运动员1"); Thread t2 = new Thread(runner, "运动员2"); Thread t3 = new Thread(runner, "运动员3"); // 3、打印默认优先级(TODO 5) System.out.println(t1.getName() + " 默认优先级:" + t1.getPriority()); // 5 // 4、设置不同优先级(TODO 6) t1.setPriority(Thread.MAX_PRIORITY); // 10 t2.setPriority(Thread.NORM_PRIORITY); // 5 t3.setPriority(Thread.MIN_PRIORITY); // 1 // 5、再次打印(TODO 7) System.out.println(t1.getName() + " 优先级改为:" + t1.getPriority()); // 10 System.out.println(t2.getName() + " 优先级改为:" + t2.getPriority()); // 5 System.out.println(t3.getName() + " 优先级改为:" + t3.getPriority()); // 1 // 6、启动 3 个线程(TODO 8) t1.start(); t2.start(); t3.start(); ``` ## 练习三:DaemonTest(守护线程心跳监控) ```java // 1、用户线程(主任务)(TODO 1~2) Thread user = new Thread(() -> { for (int i = 1; i <= 3; i++) { try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("主任务执行中...第 " + i + " 秒"); } System.out.println("主任务执行完毕!"); }, "主任务"); // 2、守护线程(心跳监控)(TODO 3) Thread heart = new Thread(() -> { int count = 0; while (true) { try { Thread.sleep(300); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println("心跳检测中...第 " + (++count) + " 次"); } }, "心跳监控"); // 3、设置守护线程(start 之前)(TODO 4) heart.setDaemon(true); // 4、验证 isDaemon(TODO 5) System.out.println("心跳监控是否是守护线程:" + heart.isDaemon()); // true System.out.println("主任务是否是守护线程:" + user.isDaemon()); // false // 5、启动(TODO 6) user.start(); heart.start(); // main 结束 → 只剩守护线程 → JVM 退出 → 心跳被强制终止 ``` ## 练习四:CooperationTest(join + interrupt + yield) ```java // 1、join + yield 演示(TODO 1~5) Thread worker = new Thread(() -> { for (int i = 0; i < 5; i++) { Thread.yield(); // 主动让出 CPU System.out.println("worker-1 工作中---> " + i); try { Thread.sleep(200); } catch (InterruptedException e) { Thread.currentThread().interrupt(); // 重设中断标志 break; } } }, "worker-1"); worker.start(); worker.join(); // 主线程等待 worker-1 结束 System.out.println("worker-1 已结束,主线程继续..."); // 2、interrupt 中断演示(TODO 6~9) Thread sleeper = new Thread(() -> { try { Thread.sleep(5000); System.out.println("任务完成"); } catch (InterruptedException e) { Thread.currentThread().interrupt(); // 重设中断标志 System.out.println("sleep 中被中断,isInterrupted()= " + Thread.currentThread().isInterrupted()); } }, "worker-2"); sleeper.start(); Thread.sleep(500); // 让 sleeper 先进入 sleep sleeper.interrupt(); // 发送中断请求 sleeper.join(); // 主线程等 worker-2 处理完中断 System.out.println("主线程结束"); ``` --- # 涵盖知识点总览 | 知识点 | 说明 | |--------|------| | `Thread.State` 枚举 | 线程 6 种状态:NEW / RUNNABLE / BLOCKED / WAITING / TIMED_WAITING / TERMINATED | | `getState()` | 返回线程当前状态的枚举值,用于观察线程生命周期 | | NEW → RUNNABLE | `new Thread()` 后未 `start()` 是 NEW;`start()` 后进入 RUNNABLE(运行或等时间片) | | TIMED_WAITING | `sleep(ms)` / `wait(timeout)` / `join(timeout)` 限时等待状态 | | TERMINATED | run() 执行完毕或抛未捕获异常结束,终点站不可逆 | | `getPriority()` | 返回线程优先级(默认 5) | | `setPriority(int)` | 修改线程优先级,范围 1~10(超出抛 IllegalArgumentException) | | 优先级常量 | `MAX_PRIORITY`(10) / `NORM_PRIORITY`(5) / `MIN_PRIORITY`(1) | | 抢占式调度模型 | 优先让优先级高的线程使用 CPU,同优先级随机选择;Java 默认采用 | | 多线程随机性 | 谁抢到 CPU 时间片不确定,执行顺序具有随机性 | | `setDaemon(boolean)` | 标记守护线程,必须在 start() 之前调用 | | `isDaemon()` | 判断线程是否是守护线程 | | JVM 退出机制 | 所有用户线程结束才退出;只剩守护线程时 JVM 直接退出并强杀守护线程 | | 守护线程特点 | 服务型线程、子线程默认继承守护属性、被强杀时 finally 不保证执行 | | `join()` | 当前线程等待目标线程执行完毕(进入 WAITING) | | `join(long)` | 最多等待指定毫秒,超过就不等(进入 TIMED_WAITING) | | `interrupt()` | 设置中断标志不杀线程;阻塞中被中断抛 InterruptedException 并清除标志 | | `isInterrupted()` | 检查中断标志(不清除);`Thread.interrupted()` 检查并清除 | | 重设中断标志 | catch 中 `Thread.currentThread().interrupt()` 恢复标志,方便外层感知 | | `yield()` | 主动让出 CPU 时间片重新竞争;只是建议,调度器不一定采纳 | | `wait()` / `notify()` / `notifyAll()` | Object 类线程通信方法:等待 / 唤醒(依赖对象锁,进 WAITING) |