说明:本次课程学习了 多线程的进阶内容——线程操作方法(
Thread.sleep()线程休眠、getPriority()/setPriority()线程优先级、抢占式调度模型与 CPU 时间片随机性、join()/interrupt()/yield()/isAlive()、Object 类wait()/notify()/notifyAll())、线程生命周期(Thread.State枚举 6 种状态:NEW / RUNNABLE / BLOCKED / WAITING / TIMED_WAITING / TERMINATED、getState()观察状态)、守护线程(setDaemon()/isDaemon()、JVM 退出机制、守护线程特点)、线程安全问题(数据竞争、卖票问题、计数器问题、线程安全三大特性:原子性 / 可见性 / 有序性)以及 synchronized 线程同步(监视器锁 Monitor Lock、三种写法:同步实例方法锁 this / 同步静态方法锁 Class 对象 / 同步代码块锁指定对象、锁粒度优化、转账业务场景)。本作业共 3 道题:基础题(必做)——线程优先级 + 生命周期观察赛跑;进阶题(必做)——守护线程后台心跳监控(含 join 等待与状态观察);挑战题(选做)——synchronized 线程同步修复卖票问题。覆盖今日核心知识点。
| 项目 | 要求 |
|---|---|
| 类名 | Demo_题号_你的姓名(如 Demo_01_张三、Demo_02_李四、Demo_03_王五) |
| 文件头注释 | 每个 Java 文件头部需包含 @author、@title、@create 文档注释 |
| 项目 | 要求 |
|---|---|
| 提交方式 | 将源码文件 + 运行结果截图提交至班级指定平台 |
| 截止时间 | 下次上课前 |
知识点:setPriority() / getPriority()(默认 5、范围 1~10)、Thread.MAX_PRIORITY / NORM_PRIORITY / MIN_PRIORITY、Thread.sleep() 线程休眠、抢占式调度模型与多线程执行随机性、线程生命周期(Thread.State 枚举、getState() 观察 NEW / RUNNABLE / TIMED_WAITING / TERMINATED)、Lambda 创建 Runnable、join() 等待线程结束
场景描述:课堂用 Demo01 演示了 Lambda 创建 Runnable + setPriority(8) 设置优先级 + sleep(100) 让双线程并发交错;用 Demo03 的 getState() 观察线程生命周期状态。请综合实现:创建 3 个"运动员"线程(优先级分别为 10 / 5 / 1)赛跑 10 圈,每圈 sleep(100) 模拟跑步;同时用 getState() 观察某个线程在不同阶段的状态变化,体会优先级对抢占调度的影响和生命周期状态流转。
题目要求:
/**
* @author 你的姓名
* @title 题目1:线程优先级 + 生命周期观察赛跑
* @create 2026/8/10
*/
public class Demo_01_姓名 {
// ========== ① Lambda 创建 Runnable 运动员任务 ==========
static Runnable runner = () -> {
// TODO 1: String name = Thread.currentThread().getName(); // 获取线程名
// TODO 2: int p = Thread.currentThread().getPriority(); // 获取优先级
// TODO 3: 循环 10 圈,每圈 sleep(100) 后打印 name + "(优先级p)跑完第 i 圈"
// TODO 4: 循环结束打印 name + " 到达终点!"
};
public static void main(String[] args) throws InterruptedException {
// ========== ② 创建 3 个运动员线程 ==========
// TODO 5: new Thread(runner, "运动员1") / "运动员2" / "运动员3"
// TODO 6: 打印 3 个线程的默认优先级(预期都是 5)
// ========== ③ 设置不同优先级 ==========
// TODO 7: t1.setPriority(Thread.MAX_PRIORITY); // 10
// t2.setPriority(Thread.NORM_PRIORITY); // 5
// t3.setPriority(Thread.MIN_PRIORITY); // 1
// ========== ④ 启动线程前观察 NEW 状态 ==========
// TODO 8: System.out.println("启动前状态:" + t1.getState()); // NEW
// ========== ⑤ 启动 3 个线程 ==========
// TODO 9: t1.start(); t2.start(); t3.start();
// ========== ⑥ 观察 t1 运行中的状态 ==========
// TODO 10: 主线程 sleep(50) 后打印 t1.getState() // 可能是 RUNNABLE 或 TIMED_WAITING
// TODO 11: 用 t1.join() 等待 t1 结束,再打印 t1.getState() // TERMINATED
}
}
预期输出示例(每次运行顺序可能不同):
运动员1 默认优先级:5
运动员2 默认优先级:5
运动员3 默认优先级:5
启动前状态:NEW
运动员1(优先级10)跑完第 1 圈
运动员3(优先级1)跑完第 1 圈
运动员2(优先级5)跑完第 1 圈
...
运行中状态:TIMED_WAITING
...
运动员1 到达终点!
结束后状态:TERMINATED
要求说明:
getState() 至少观察 NEW / TIMED_WAITING / TERMINATED 三个状态(RUNNABLE 尽量观察)t1.join() 等待 t1 结束💡 提示:
Thread.MAX_PRIORITY=10、NORM_PRIORITY=5(默认)、MIN_PRIORITY=1,setPriority() 超范围抛 IllegalArgumentExceptionsleep(100) 期间线程处于 TIMED_WAITING;new Thread() 未 start 是 NEW;run() 结束是 TERMINATEDgetState() 返回 Thread.State 枚举:NEW / RUNNABLE / BLOCKED / WAITING / TIMED_WAITING / TERMINATED🎯 挑战加分(选做):
setPriority(11) 传入试试,观察抛出的 IllegalArgumentExceptionsynchronized 锁,观察未抢到锁线程的 getState() 是否为 BLOCKED(提前预习线程同步)知识点:守护线程(setDaemon(true) / isDaemon()、JVM 退出机制:所有用户线程结束才退出、只剩守护线程时 JVM 强制终止守护线程)、守护线程特点(必须在 start() 前设置、被强杀时 finally 不保证执行)、用户线程 vs 守护线程对比、join() 等待线程结束、sleep() 线程休眠、线程生命周期状态观察
场景描述:课堂用 Demo02 演示了守护线程——"主任务(用户线程)" + "心跳监控(守护线程)",main 结束后 JVM 退出、守护线程被强制终止。请实现:主任务线程执行 5 秒业务(每秒打印一次进度),心跳监控线程(守护线程)每 500ms 报告一次心跳;主线程用 join() 等待主任务结束,观察 main 结束后心跳线程是否被 JVM 强制终止,并打印 isDaemon() 验证两个线程的守护属性。
题目要求:
/**
* @author 你的姓名
* @title 题目2:守护线程后台心跳监控 + join 等待
* @create 2026/8/10
*/
public class Demo_02_姓名 {
public static void main(String[] args) throws InterruptedException {
// ========== ① 用户线程(主任务):执行 5 秒 ==========
Thread task = new Thread(() -> {
for (int i = 1; i <= 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 重设中断标志
break;
}
// TODO 1: 打印 "主任务执行中...第 i 秒"
}
// TODO 2: 打印 "主任务执行完毕!"
}, "主任务");
// ========== ② 守护线程(心跳监控):无限循环 ==========
Thread heart = new Thread(() -> {
int count = 0;
while (true) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
// TODO 3: 打印 "心跳检测中...第 (++count) 次"
}
}, "心跳监控");
// ========== ③ 设置守护线程(必须在 start() 之前!)==========
// TODO 4: heart.setDaemon(true);
// ========== ④ 验证 isDaemon ==========
// TODO 5: 打印 heart.isDaemon()(预期 true)和 task.isDaemon()(预期 false)
// ========== ⑤ 启动两个线程 ==========
// TODO 6: task.start(); heart.start();
// ========== ⑥ 主线程 join 等待主任务结束 ==========
// TODO 7: task.join(); 主线程等待主任务执行完
// TODO 8: 打印 "main 结束,观察心跳线程是否还在打印"
// main 结束后只剩守护线程 → JVM 退出 → 心跳线程被强制终止
}
}
预期输出示例(每次运行顺序可能不同):
心跳监控是否是守护线程:true
主任务是否是守护线程:false
主任务执行中...第 1 秒
心跳检测中...第 1 次
心跳检测中...第 2 次
心跳检测中...第 3 次
主任务执行中...第 2 秒
...
主任务执行完毕!
main 结束,观察心跳线程是否还在打印
← main 结束,JVM 退出,心跳检测不再打印
要求说明:
setDaemon(true) 标记为守护线程,且必须在 start() 之前调用isDaemon() 验证两个线程的守护属性(主任务 false、心跳 true)task.join() 等待主任务执行结束💡 提示:
finally 不保证执行(所以不要用守护线程做资源清理)heart.setDaemon(true) 移到 heart.start() 之后会抛 IllegalThreadStateException🎯 挑战加分(选做):
setDaemon(true) 移到 start() 之后运行,观察抛出的 IllegalThreadStateException知识点:线程安全问题(数据竞争、进程间不共享 vs 线程间共享)、卖票问题(相同票多次出现 / 负数票两个 Bug 及其原因:读-改-写被并发打断)、线程安全三大特性(原子性 / 可见性 / 有序性)、synchronized 三种写法(同步实例方法锁 this / 同步静态方法锁 Class 对象 / 同步代码块锁指定对象)、监视器锁(Monitor Lock)、锁粒度优化、join() 等待线程结束
场景描述:课堂 Demo04 + SellTicket 演示了卖票问题——3 个售票窗口线程同时卖 100 张票,出现"相同的票多次出现"和"负数票"两个 Bug;SynchronizedDemo 用 synchronized 解决。请完成:先写无 synchronized 的卖票代码复现 Bug,再用 synchronized 三种写法(同步方法 / 同步代码块锁 this / 同步代码块锁自定义对象)修复,最后验证总票数正确、无重复、无负数。
题目要求:
/**
* @author 你的姓名
* @title 题目3:synchronized 线程同步修复卖票问题
* @create 2026/8/10
*/
public class Demo_03_姓名 {
// ========== ① 卖票类:实现 Runnable ==========
static class SellTicket implements Runnable {
private int tickets = 100; // 一共 100 张票
// 写法1:同步方法(锁 this)—— TODO 2 完成后给方法加 synchronized
public void sell() {
// TODO 1: if (tickets > 0) { sleep(10); 打印 "xxx正在出售第 tickets 张票"; tickets--; }
}
@Override
public void run() {
while (tickets > 0) {
sell(); // TODO 2: 先不加 synchronized 运行复现 Bug,再加 synchronized 修复
}
}
}
public static void main(String[] args) throws InterruptedException {
// ========== ② 创建 3 个售票窗口线程(共用同一个 SellTicket 对象) ==========
// TODO 3: new Thread(new SellTicket(), "窗口1") / "窗口2" / "窗口3",start() 启动
// TODO 4: 用线程数组 + join() 等待所有线程卖票结束
// TODO 5: 运行多次观察:无 synchronized 时出现相同票 / 负数票;加 synchronized 后消失
}
}
预期输出示例(无 synchronized 时——Bug 复现):
窗口1正在出售第10张票
窗口2正在出售第10张票 ← Bug1:相同的票多次出现
窗口3正在出售第10张票
...
窗口1正在出售第1张票
窗口2正在出售第0张票 ← Bug2:负数票
窗口3正在出售第-1张票
预期输出示例(加 synchronized 修复后):
窗口1正在出售第100张票
窗口2正在出售第99张票
窗口3正在出售第98张票
...
窗口3正在出售第1张票
全部窗口卖票结束!共卖出100张票,无重复、无负数
要求说明:
sell() 加 synchronized 修复private final Object lock = new Object(); synchronized(lock){...})实现第二种写法join() 等待所有窗口线程结束,最后统计验证总票数💡 提示:
if (tickets > 0)(判断)与 tickets--(修改)之间是读-改-写复合操作,抢占式调度下线程随时可能被切换走,另一个线程趁机插入操作共享数据public synchronized void sell() 锁 this;②同步静态方法 public static synchronized 锁 Class 对象;③同步代码块 synchronized(lock){} 锁指定对象(最灵活、粒度最细)🎯 挑战加分(选做):
synchronized(this) 写一个转账方法 transfer(int amount):把"检查余额 ≥ amount"和"余额 -= amount"整体锁住,模拟多个线程同时转账,验证余额不为负sell() 中加 Thread.sleep(50) 放大并发窗口,观察不加锁时 Bug 复现更明显;加锁后再运行验证修复效果Demo_题号_姓名(如 Demo_01_张三、Demo_02_李四、Demo_03_王五)@author(你的姓名)、@title(题目名称)、@create 2026/8/10synchronized 关键字,JDK 自带)