说明:练习今日新学知识点——线程操作方法(
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()等待唤醒)。
难度:⭐⭐⭐
知识点: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() 方法观察一个线程在"创建→启动→休眠→结束"各阶段的状态。
start() 之前打印状态 → 预期 NEWstart() 之后立即打印状态 → 预期 RUNNABLE(或运行中)join() 等待子线程结束后打印状态 → 预期 TERMINATEDpublic 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
选做加分:
getState(),对比多个线程并发时的状态wait()(需 synchronized 同步块),在主线程打印它的状态观察 WAITINGsynchronized 锁,观察未抢到锁的线程状态是否为 BLOCKED思考题:
getState() 在 start() 之后立即调用,返回的多半是 RUNNABLE 而不是 RUNNING?(提示:Java 把"运行"和"就绪"合并成一个 RUNNABLE 状态,无法精确区分)sleep() 期间线程处于什么状态?(提示:TIMED_WAITING 计时等待,超时自动恢复)join() 让调用线程进入什么状态?(提示:WAITING 无限等待;join(timeout) 进入 TIMED_WAITING)TERMINATED 之后就"回不去"了?(提示:TERMINATED 是终点站,run() 结束不可逆)难度:⭐⭐⭐
知识点: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),观察到达终点的先后顺序。
MAX_PRIORITY(10) / NORM_PRIORITY(5) / MIN_PRIORITY(1)start() 启动 3 个线程,观察输出交错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) 传入试试,观察抛出的 IllegalArgumentExceptionThread.sleep(100) 再运行,观察输出速度与交错变化——sleep 放慢速度便于观察思考题:
Thread.sleep(100) 在这个例子中起什么作用?(提示:主动让出时间片、放慢速度,让并发输出交错更明显)难度:⭐⭐⭐⭐
知识点:setDaemon(boolean)、isDaemon()、用户线程 vs 守护线程、JVM 退出条件(所有用户线程结束才退出)、守护线程特点(start() 前设置、被强杀时 finally 不保证执行)
场景描述:课堂学了守护线程:它是服务型线程,专门为用户线程提供后台支持;只要还有一个用户线程存活,JVM 就不会退出,而只剩守护线程时 JVM 直接退出,守护线程被强制终止。本例实现一个"主任务(用户线程)+ 心跳监控(守护线程)"的场景,验证 main 结束后 JVM 是否退出、守护线程是否被强制终止。
创建主任务线程(用户线程,执行 3 秒)和心跳监控线程(守护线程,无限循环报告心跳)。观察 main 结束后:用户线程还在跑时 JVM 是否退出?守护线程是否还在打印?
start() 之前调用 heart.setDaemon(true)isDaemon(),验证守护属性start() 启动两个线程,观察 main 结束后的输出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(true) 为什么必须在 start() 之前调用?(提示:线程启动后守护属性不可修改,否则抛 IllegalThreadStateException)难度:⭐⭐⭐⭐
知识点: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 三种线程操作方法的协作场景。
worker.join() 等它结束interrupt() 中断它,观察 catch 中抛异常、重设标志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 轮)Thread.currentThread().interrupt(),观察外层 isInterrupted() 的值变化——体会"InterruptedException 抛出时标志被清除"while (true) + if (Thread.currentThread().isInterrupted()) break;,用 interrupt() 优雅地结束无限循环线程思考题:
join() 和 join(long) 的区别是什么?(提示:join() 无限等待目标线程结束;join(millis) 最多等待指定毫秒,超过就不等)interrupt() 会杀死线程吗?(提示:不会!只是设置中断标志,请求线程自行响应,是否退出由线程自己决定)InterruptedException 抛出的同时会清除中断标志?(提示:阻塞方法被中断时先清标志再抛异常;catch 后重新 interrupt() 是为了让外层 isInterrupted() 能感知到中断)yield() 和 sleep(ms) 有什么区别?(提示:yield 只是让出一次 CPU 回到 RUNNABLE,可能马上又被调度;sleep 是限时休眠进入 TIMED_WAITING)// 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
// 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();
// 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 退出 → 心跳被强制终止
// 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) |