笔记依旧是有参考:JavaNote/Prog.md at main · Seazean/JavaNote
线程方法
API
Thread 类 API
| 方法 | 说明 |
|---|---|
| public void start() | 启动一个新线程,Java虚拟机调用此线程的 run 方法 |
| public void run() | 线程启动后调用该方法 |
| public void setName(String name) | 给当前线程取名字 |
| public void getName() | 获取当前线程的名字 线程存在默认名称:子线程是 Thread-索引,主线程是 main |
| public static Thread currentThread() | 获取当前线程对象,代码在哪个线程中执行 |
| public static void sleep(long time) | 让当前线程休眠多少毫秒再继续执行 Thread.sleep(0) : 让操作系统立刻重新进行一次 CPU 竞争 |
| public static native void yield() | 提示线程调度器让出当前线程对 CPU 的使用 |
| public final int getPriority() | 返回此线程的优先级 |
| public final void setPriority(int priority) | 更改此线程的优先级,常用 1 5 10 |
| public void interrupt() | 中断这个线程,异常处理机制 |
| public static boolean interrupted() | 判断当前线程是否被打断,清除打断标记 |
| public boolean isInterrupted() | 判断当前线程是否被打断,不清除打断标记 |
| public final void join() | 等待这个线程结束 |
| public final void join(long millis) | 等待这个线程死亡 millis 毫秒,0 意味着永远等待 |
| public final native boolean isAlive() | 线程是否存活(还没有运行完毕) |
| public final void setDaemon(boolean on) | 将此线程标记为守护线程或用户线程 |
1. CPU 竞争是什么?
CPU 就像一个“超级服务员”,它一次只能服务一个“顾客”(线程)。但系统里有成百上千个线程都想让 CPU 服务自己。所以这些线程就得“抢”这个服务员——这就是 CPU 竞争。当你调用 Thread.sleep(0) 或 Thread.yield(),你是在说:“我先不忙了,让别人也来抢一下服务员吧!”
sleep(0):虽然睡0秒,但会立刻触发一次“重新排队抢CPU”的机会。
yield():是礼貌地“让贤”:“我先歇会儿,你们谁想上就上。”
根据以上内容可以写一个demo:
public class CPUSleepZeroDemo {
public static void main(String[] args) {
new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("小弟我在干活:" + i);
if (i == 2) {
System.out.println(">> 我让出CPU,大家快来抢!");
try {
Thread.sleep(0); // 主动触发一次CPU竞争
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
// 主线程也打印点东西,看谁抢到CPU
for (int i = 0; i < 5; i++) {
System.out.println(" 主线程在打印:" + i);
}
}
}
代码输出如下:

2. 打断标记是什么?有什么用?
每个线程都有一个“小红点”(打断标记),表示“有人想让你停下来”。
- 调用
thread.interrupt()→ 给那个线程贴上“小红点”(打断标记设为 true) - 线程自己可以通过
isInterrupted()查看有没有小红点 - 如果发现有小红点,就可以优雅地收工:“哦,领导让我下班,那我收拾东西走人。”
注意:贴小红点 ≠ 强制停止线程!只是“通知”你该停了。
根据以上内容可以写一个demo:
public class InterruptDemo {
public static void main(String[] args) throws InterruptedException {
Thread worker = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
System.out.println("程序员正在写第 " + i + " 行代码...");
// 每写一行都检查一下:老板喊我下班了吗?
if (Thread.currentThread().isInterrupted()) {
System.out.println("收到下班通知!保存工作,准备走人...");
return; // 优雅退出
}
try {
Thread.sleep(100); // 写代码累了休息一下
} catch (InterruptedException e) {
// 注意!sleep时被interrupt会抛异常,同时清除标记
System.out.println("休息时被通知下班了!");
return; // 直接退出
}
}
});
worker.start();
Thread.sleep(500); // 让他干一会儿活
worker.interrupt(); // 老板喊:下班了!
}
}
isInterrupted():看有没有被打断(不擦掉小红点)interrupted():看有没有被打断(会擦掉小红点)sleep()中被打断会抛异常,并清除标记
输出的结果如下:

3. 守护线程与用户线程是什么?
大白话解释:
- 用户线程:正经员工,公司靠他们干活。只要还有一个正经员工在上班,公司就不能关门。
- 守护线程:保安、保洁。他们只在有人上班时才工作。一旦所有人都走了,他们也自动下班。
对应到Java:
- 主线程、你自己创建的线程默认是用户线程
- 守护线程(比如垃圾回收线程):当所有用户线程结束,JVM 就会关闭,不管守护线程干到哪了。
又可以来写一个demo来演示:
public class DaemonDemo {
public static void main(String[] args) {
Thread userThread = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("我是正式员工,还在工作:" + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(">> 正式员工下班了!");
});
Thread daemonThread = new Thread(() -> {
while (true) {
System.out.println("我是保安,正在巡逻...");
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
// 必须在 start() 之前设置为守护线程
daemonThread.setDaemon(true);
userThread.start();
daemonThread.start();
System.out.println("主线程结束。");
}
}
输出结果如下:

| 概念 | 大白话 | 关键方法 | 生活比喻 |
|---|---|---|---|
| CPU 竞争 | 多个线程抢CPU执行权 | sleep(0), yield() | 奶茶店排队,我让别人先买 |
| 打断标记 | 别人想让我停下来 | interrupt(), isInterrupted() | 老板说“可以下班了” |
| 守护线程 | 服务用户线程的“辅助人员” | setDaemon(true) | 保安:人走灯灭,立刻下班 |
run与start
start():是“按下启动键”,系统会开一个新线程去执行run()里的代码。()run():是“直接干活”,但它只是普通方法调用,不会开新线程,就在当前线程里一步一步执行。
举个做饭的例子吧。
想象你是一个人(主线程),要做三件事:
- 煮饭(需要10分钟)
- 炒菜(需要5分钟)
- 洗碗(需要3分钟)
效率不高的做法:直接调用 run()(顺序执行)
你一个人干完一件再干下一件:
class CookRice implements Runnable {
public void run() {
System.out.println("开始煮饭...");
try {
Thread.sleep(10000); // 模拟煮饭10秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("煮饭完成!");
}
}
class StirFry implements Runnable {
public void run() {
System.out.println("开始炒菜...");
try {
Thread.sleep(5000); // 模拟炒菜5秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("炒菜完成!");
}
}
public class RunMain {
public static void main(String[] args) {
CookRice cookRice = new CookRice();
StirFry stirFry = new StirFry();
System.out.println("主线程开始");
// 直接调用 run() —— 顺序执行,效率低
cookRice.run(); // 先煮饭,等10秒
stirFry.run(); // 再炒菜,等5秒
System.out.println("所有任务完成!");
}
}
代码输出如下:

效率高的做法:使用 start()(并发执行)
使用 start 是启动新的线程,此线程处于就绪(可运行)状态,通过新的线程间接执行 run 中的代码。说明:线程控制资源类。相当于请了一个帮手(新线程)来煮饭,你自己炒菜:
public class StartMain {
public static void main(String[] args) {
CookRice cookRice = new CookRice();
StirFry stirFry = new StirFry();
Thread riceThread = new Thread(cookRice); // 把煮饭任务交给新线程
Thread stirFryThread = new Thread(stirFry); // 把炒菜任务交给另一个线程
System.out.println("主线程开始");
// 使用 start() —— 启动新线程并发执行
riceThread.start(); // 启动煮饭线程(帮手开始干活)
stirFryThread.start(); // 启动炒菜线程(你自己开始炒菜)
// 主线程可以干别的,比如等他们做完
try {
riceThread.join(); // 等煮饭完成
stirFryThread.join(); // 等炒菜完成
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("所有任务完成!");
}
}
这时的代码输出如下:

run() 方法中的异常不能抛出,只能 try/catch
- 因为父类中没有抛出任何异常,子类不能比父类抛出更多的异常
- 异常不能跨线程传播回 main() 中,因此必须在本地进行处理
更进一步解释:
run() 方法来自 Runnable 接口,它的定义是:
public void run();
它没有 throws Exception。所以你在重写 run() 时,不能抛出受检异常。
public void run() throws IOException { // 编译报错!
FileReader file = new FileReader("xxx.txt"); // 可能抛 IOException
}
这是错误的写法。
public void run() {
try {
FileReader file = new FileReader("xxx.txt");
// 读文件...
} catch (IOException e) {
System.out.println("文件读取失败:" + e.getMessage());
// 在这里处理异常,不能往外抛
}
}
这是正确的写法。为什么?因为新线程是独立的,它的异常传不回主线程。就像你请的帮手在厨房炸锅了,你不处理,没人知道。所以必须在线程内部 try/catch。
sleep与yield
Thread.sleep() —— “我先睡会儿,别叫醒我,除非出大事!”
- 调用 sleep 会让当前线程从
Running进入Timed Waiting状态(阻塞,就是在睡觉) - sleep() 方法的过程中,线程不会释放对象锁(睡觉期间抱着一个资源睡觉,别人拿不走)
- 其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException(如果有人强行叫醒你(
interrupt()):你会惊醒,并抛出InterruptedException) - 睡眠结束后的线程未必会立刻得到执行,需要抢占 CPU
可以根据以上写一个demo:
public class SleepDemo {
private static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
Thread coder = new Thread(() -> {
synchronized (lock) {
System.out.println("(coder) 我拿到文档,开始写代码...");
try {
System.out.println("(coder) 我要睡5秒...");
Thread.sleep(5000); // 睡5秒
} catch (InterruptedException e) {
System.out.println("(coder) 被叫醒了!出大事了!");
return;
}
System.out.println("(coder) 睡醒了,继续写代码...");
}
});
coder.start();
Thread.sleep(2000); // 2秒后
new Thread(() -> {
synchronized (lock) {
System.out.println("(tester) 我想测试,但文档被占着,只能等...");
}
}).start();
Thread.sleep(1000);
System.out.println(">> 主线程决定打断他!");
coder.interrupt(); // 强行叫醒
}
}
输出结果如下:

- 建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性
// 不推荐
Thread.sleep(3600000); // 360万毫秒?这是几小时?
// 推荐
TimeUnit.HOURS.sleep(1); // 睡1小时
TimeUnit.MINUTES.sleep(30); // 睡30分钟
TimeUnit.SECONDS.sleep(5); // 睡5秒
例子依旧可以见上。
Thread.yield() —— “我先让一下,你们谁想上就上!”
- 调用
yield():当前线程主动放弃CPU,回到“就绪状态”(调用 yield 会让提示线程调度器让出当前线程对 CPU 的使用) - 是否让出成功?不保证! 完全看操作系统心情(具体的实现依赖于操作系统的任务调度器)
- 会放弃 CPU 资源,锁资源不会释放
也可以写一个简单的demo:
public class YieldDemo {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("T1: 唱歌第 " + i + " 句");
if (i == 2) {
System.out.println("T1: 我让一下,你们谁想唱?");
Thread.yield(); // 让一下
}
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println(" T2: 我来唱!第 " + i + " 句");
}
});
t1.start();
t2.start();
}
}
输出结果每次可能不相同。
线程t2先的输出:

线程t1先的输出:

这就说明yield() 只是建议,不保证一定让出成功。
实际开发中怎么选?
首先对两者进行对比:
| 特性 | sleep() | yield() |
|---|---|---|
| 是否阻塞 | 是(进入 Timed Waiting) | 否(进入就绪状态) |
| 是否释放锁 | 不释放 | 不释放 |
| 是否准时醒来 | 是(时间到就醒) | —— |
| 是否可被打断 | 可被 interrupt 打断 | 不会抛异常 |
| 是否让出CPU | 一定会让出一段时间 | 不一定成功(看系统) |
| 下一秒能否继续执行 | 需重新抢CPU | 可能立刻又抢到 |
| 推荐使用 | TimeUnit.SECONDS.sleep(5) | 很少用,效果不确定 |
- 用
sleep(1)或sleep(0)来让出CPU(比yield()更可靠) - 避免单独使用
yield(),因为效果不保证 - 需要“暂停执行”就用
sleep - 需要“优雅等待”可以用
sleep(0)触发一次调度竞争
// 让当前线程退出CPU,强制重新排队
// 相当于“我先下台,让别人也有机会上”
Thread.sleep(0);
这比 yield() 更有效,因为 sleep(0) 一定会触发调度器重新决策。
join
t1.join()的意思就是:“我(调用者线程)要等你(t1线程)干完活,我才继续往下走。”
就像你约朋友吃饭,你说:“你到了餐厅我再点菜。” —— 你就在那儿等着,直到朋友到。
再举一个做蛋糕的生活例子。假设你要做一个蛋糕,流程是:
- 烤蛋糕(需要5分钟)—— 由帮手 A 负责
- 装饰蛋糕(需要2分钟)—— 你自己来做
但你不能先装饰,必须等蛋糕烤好了才能开始装饰。
不用 join():你不管帮手,直接开始装饰(出错!)
public class CakeMaker {
static String cake = "生面团";
public static void main(String[] args) {
Thread bakeThread = new Thread(() -> {
System.out.println("帮手A:开始烤蛋糕...");
try {
Thread.sleep(5000); // 模拟烤5秒
} catch (InterruptedException e) {
e.printStackTrace();
}
cake = "熟蛋糕";
System.out.println("帮手A:蛋糕烤好了!");
});
bakeThread.start(); // 启动烤蛋糕线程
// 没有等待,直接开始装饰
System.out.println("我:开始装饰蛋糕...");
if (cake.equals("熟蛋糕")) {
System.out.println("装饰完成!可以吃了!");
} else {
System.out.println("失败!蛋糕还是生的,不能装饰!");
}
}
}
代码输出如下:

你没等帮手,蛋糕还是“生面团”你就开始装饰了,很明显这是不符合常理的。
想要等烤好了再装饰,就需要用到 join(),等帮手烤好后再装饰。
public class CakeMaker2 {
static String cake = "生面团";
public static void main(String[] args) throws InterruptedException {
Thread bakeThread = new Thread(() -> {
System.out.println("帮手A:开始烤蛋糕...");
try {
Thread.sleep(5000); // 模拟烤5秒
} catch (InterruptedException e) {
e.printStackTrace();
}
cake = "熟蛋糕";
System.out.println("帮手A:蛋糕烤好了!");
});
bakeThread.start(); // 启动烤蛋糕线程
// 加上这一句:我(主线程)要等 bakeThread 干完
bakeThread.join();
// 只有等烤好后,我才继续执行下面的代码
System.out.println("我:开始装饰蛋糕...");
if (cake.equals("熟蛋糕")) {
System.out.println("装饰完成!可以吃了!");
}
}
}
用了 join(),主线程被阻塞,一直等到 bakeThread 结束才继续,所以蛋糕一定是熟的!代码输出结果如下:

接下来再来探讨一下 join() 的底层原理:调用者轮询检查线程 alive 状态。你可以把 join() 想象成这样一段代码:
// 伪代码:join 的原理
public final void join() throws InterruptedException {
while (isAlive()) { // 只要这个线程还活着
wait(0); // 我(调用者)就在这等,啥也不干
}
}
isAlive():检查那个线程还在不在运行。wait(0):调用者线程进入等待状态,释放的是当前线程对象的锁(后面讲)。- 一旦
bakeThread执行完,isAlive()变成false,跳出循环,join()结束,主线程继续。
注意:
join()方法是synchronized的,它用的是线程对象本身作为锁。
比如t1.join(),锁的是t1这个对象。wait()释放的也是t1的锁,不会影响其他锁。
-
join 方法是被 synchronized 修饰的,本质上是一个对象锁,其内部的 wait 方法调用也是释放锁的,但是释放的是当前的线程对象锁,而不是外面的锁
-
当调用某个线程(t1)的 join 方法后,该线程(t1)抢占到 CPU 资源,就不再释放,直到线程执行完毕
但是 join() 也有缺点:
| 问题 | 说明 |
|---|---|
| 必须等线程结束 | 不能设置超时(可以用 join(long millis) 超时等待) |
| 不能配合线程池 | 线程池里的线程是复用的,你没法 join() 一个池子里的线程 |
| 需要共享变量 | 像上面的 cake 是全局变量,破坏封装性,容易出错 |
| 不够灵活 | 只能等,不能取消、不能获取返回值 |
线程同步:
- join 实现线程同步,因为会阻塞等待另一个线程的结束,才能继续向下运行
- 需要外部共享变量,不符合面向对象封装的思想
- 必须等待线程结束,不能配合线程池使用
- Future 实现(同步):get() 方法阻塞等待执行结果
- main 线程接收结果
- get 方法是让调用线程同步等待
更好的替代方案:Future(支持返回值 + 更灵活)
用 ExecutorService 和 Future,可以拿到线程的执行结果,还能取消任务。接下来可以优化烤蛋糕代码:
import java.util.concurrent.*;
public class BetterCakeMaker {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService pool = Executors.newFixedThreadPool(2);
// 提交一个能返回结果的任务
Future<String> future = pool.submit(() -> {
System.out.println("帮手A:开始烤蛋糕...");
Thread.sleep(5000);
System.out.println("帮手A:蛋糕烤好了!");
return "熟蛋糕"; // 可以返回结果!
});
// get() 就像 join(),但还能拿到返回值
String cake = future.get(); // 阻塞等待,直到有结果
System.out.println("我:开始装饰 " + cake);
System.out.println("装饰完成!可以吃了!");
pool.shutdown();
}
}
代码输出如下:

优点:
- 能拿到返回值
- 可以设置超时:
future.get(3, TimeUnit.SECONDS) - 可以取消:
future.cancel(true) - 配合线程池,更高效
interrupt
线程打断 ≠ 停止线程!
它就像你给一个正在干活的人递了个“请尽快收工”的小纸条。
他看到了,可以选择:
- 马上停下
- 先把手头事干完再停
- 甚至假装没看见(不推荐)
Java 不允许强制停止线程,只能“礼貌地请求”中断。
举一个生活例子,快递员送快递。
假设你是一个快递员(线程 t1),正在一条街一条街地送快递(循环干活)。
老板突然打电话说:“今天台风要来了,你尽快收工回家!”
这就是 “打断” —— 不是直接把你抓回来,而是告诉你:“该收工了。”
你听到后可以:
- 马上回家(响应中断)
- 把手上这单送完再走(合理)
- 装听不见,继续送(危险,不推荐)
| 方法 | 大白话解释 | 是否清空标记 |
|---|---|---|
t1.interrupt() | 我要打断线程 t1!发个“中断请求” | 不清空,只是设置标记 |
Thread.interrupted() | 当前线程是否被中断过?是静态方法! | 会清除标记(只看一次) |
t1.isInterrupted() | 线程 t1 是否被中断过? | 不清除标记(可反复看) |
关键区别:interrupted() 是静态方法,看的是“当前线程”,而且看一眼就清空标记!
写一个打断正在 sleep 的线程(会抛异常):
public class InterruptSleepDemo {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
System.out.println("t1:我开始睡觉了,睡10秒...");
try {
Thread.sleep(10000); // 睡10秒
} catch (InterruptedException e) {
System.out.println("t1:啊!我睡着时被人打断了!异常来了!");
System.out.println("t1:我的中断标记现在是:" + Thread.currentThread().isInterrupted());
// 注意:sleep 被打断后,中断标记会被 JVM 自动清除!
}
System.out.println("t1:我醒了,继续干活...");
}, "t1");
t1.start();
Thread.sleep(500); // 主线程等500毫秒,让t1进入sleep
System.out.println("主线程:我来打断 t1!");
t1.interrupt(); // 发出中断请求
// 等t1执行完
t1.join();
System.out.println("主线程:任务结束");
}
}
码输出如下:

sleep()中被打断 → 抛出InterruptedException- 抛出异常后,JVM 会自动清除中断标记(变
false) - 所以你在 catch 里看到
isInterrupted()是false
这是 Java 的设计:阻塞方法(sleep/wait/join)被打断,会清空中断标记,并抛异常。
再写一个打断一个正在运行的线程(不会抛异常,靠手动检查):
package com.cg.jucproject.demo;
public class InterruptRunningDemo {
public static void main(String[] args) throws InterruptedException {
Thread t2 = new Thread(() -> {
int i = 0;
while (true) {
// 手动检查是否被中断
// 只有当线程不在阻塞状态,而是在正常运行中被中断,并且手动检查了中断标记,走那 if 分支
if (Thread.currentThread().isInterrupted()) {
System.out.println("t2:有人打断我了!我要退出了!");
System.out.println("t2:中断标记是:" + Thread.currentThread().isInterrupted());
break;
}
// 模拟干活
i++;
// 加个小小暂停,不然太快看不清
try {
Thread.sleep(100);
} catch (InterruptedException e) {
System.out.println("t2:我在干活时被 sleep 打断了!");
break;
}
}
System.out.println("t2:任务结束,i=" + i);
}, "t2");
t2.start();
Thread.sleep(500); // 等500毫秒
System.out.println("主线程:我来打断 t2!");
t2.interrupt(); // 设置中断标记为 true
t2.join(); // 等t2结束
System.out.println("主线程:任务结束");
}
}

打断 park
LockSupport.park()就像你按了“暂停键”,线程暂停运行;
别人调interrupt()就像按了“强制唤醒键”;
但和sleep不同:park被打断后,中断标记不会被清空,而且下次park可能直接失效!
举一个遥控器暂停电视的例子。
想象你在看电视(线程运行),你按了“暂停”键(park()),画面停了。
这时候你妈进来,直接按了“关机”遥控键(interrupt()),电视被强制唤醒,自动退出暂停状态。但注意:
- 电视知道是“被强行关的”,记录下这个事件(中断标记 =
true) - 你想再按“暂停”,发现:没反应! 因为系统说:“你已经被关过一次了,不能再暂停了。”
这就是 park 被打断后的“一次性”特性。
继续来写一个demo:park 被打断,不会清空中断标记
import java.util.concurrent.locks.LockSupport;
public class ParkInterruptDemo {
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(() -> {
System.out.println("t1: 我要暂停了 (park...)");
// 线程暂停,等待被 unpark 或 interrupt
LockSupport.park();
System.out.println("t1: 我被唤醒了 (unpark...)");
// 检查中断状态
System.out.println("t1: 打断状态:" + Thread.currentThread().isInterrupted());
// 输出:true!因为 interrupt 不会清空中断标记
}, "t1");
t1.start();
Thread.sleep(1000); // 主线程等1秒,让 t1 进入 park
System.out.println("主线程:我来打断 t1!");
t1.interrupt(); // 打断 t1
}
}
代码输出如下:

t1.interrupt()成功唤醒了park()- 但
isInterrupted()是true→ 中断标记没有被清空! - 对比
sleep():如果这里用sleep(1000),被打断后标记会被清空(变false)
park()和sleep()最大区别之一:park被打断不自动清标记
举一个例子:打断标记为 true 时,park() 会直接失效
public class ParkTwiceDemo {
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(() -> {
System.out.println("第一次 park...");
LockSupport.park();
System.out.println("第一次 unpark");
System.out.println("第二次 park...(此时中断标记是 true)");
LockSupport.park(); // 这里不会阻塞!直接跳过!
System.out.println("第二次 unpark"); // 立刻执行
}, "t1");
t1.start();
Thread.sleep(1000); // 主线程等1秒,让 t1 进入 park
System.out.println("主线程:我来打断 t1!");
t1.interrupt(); // 设置中断标记为 true,并唤醒第一次 park
}
}
代码输出如下:

- 第一次
park()被interrupt()唤醒,中断标记保持true - 第二次
park()发现:“咦?中断标记已经是 true 了?” → 直接认为“你已经被唤醒过了”,所以不阻塞,直接跳过 - 所以“第二次 unpark”立刻执行,没有停顿
这就是
park()的“防重入”机制:如果中断标记为true,park()直接失效,不会阻塞。
解决方案:用 Thread.interrupted() 清空中断标记
如果你想让第二次 park() 有效,就必须手动清空中断标记。
import java.util.concurrent.locks.LockSupport;
public class ParkFixedDemo {
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(() -> {
System.out.println("第一次 park...");
LockSupport.park();
System.out.println("第一次 unpark");
System.out.println("第二次 park...(此时中断标记是 true)");
LockSupport.park(); // 不会阻塞,直接跳过
System.out.println("第二次 unpark"); // 立刻执行
}, "t1");
t1.start();
Thread.sleep(1000); // 等1秒,确保 t1 进入 park
System.out.println("主线程:我来打断 t1!");
t1.interrupt(); // 打断 t1,唤醒第一次 park
}
}
Thread.interrupted() 是静态方法,它会:
- 返回当前线程是否被中断过
- 自动清空中断标记
| 特性 | Thread.sleep() | LockSupport.park() |
|---|---|---|
| 被打断时是否抛异常 | 抛 InterruptedException | 不抛异常 |
| 被打断后是否清空中断标记 | 清空(变 false) | 不清空(保持 true) |
中断标记为 true 时再次调用 | 仍会阻塞 | 直接失效,不阻塞 |
| 是否响应中断 | 是 | 是,但方式不同 |
| 底层实现 | JVM 层面 | 基于 Unsafe,更底层 |
ReentrantLock、Semaphore、CountDownLatch、FutureTask等底层都用park/unpark来控制线程阻塞和唤醒。- 它比
wait/notify更灵活:不需要 synchronized,也不需要锁对象。 unpark(thread)可以提前发,park()后来收到,不会丢(类似“许可证”机制)。

被折叠的 条评论
为什么被折叠?



