文章目录
📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌
📙 作者: 编程技术圈(哇哥面试陪跑)
👉 欢迎关注、分享、评论
✔️ 持续分享更多干货内容
🌐🌏🌎➕tcmeta, 欢迎沟通交流
📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌
零、引入
王二的脸终于不灰了,反倒透着点红光。上次用 LockSupport 改好线程代码后,领导查日志时多看了他两眼,说了句 “这代码写得像回事”—— 这是王二进公司半年来,第一次被领导夸。
可没高兴两天,新的问题又来了:他用 LockSupport 写的生产者消费者代码,线程偶尔会 “假死”;用 park 处理中断时,没判断状态导致逻辑出错。王二抱着电脑蹲在哇哥工位旁,活像个讨教武功的小徒弟:“哇哥,LockSupport 实战里的坑咋这么多?”
哇哥正对着 AQS 源码啃得入神,闻言抬了抬眼:“光会用 park 和 unpark 是皮毛,实战要管中断、防假死、懂源码,这才是真东西。今天把 LockSupport 的实战大招教给你,以后线程阻塞唤醒的活,你包圆了都没问题。”
点赞 + 关注,跟着哇哥和王二,吃透 LockSupport 实战技巧,从生产者消费者到 AQS 源码,全给你讲透,再也不背锅!

一、王二的新坑:LockSupport 实战踩的 “两个大雷”
王二写的生产者消费者代码,用 LockSupport 实现,看着没问题,跑久了就出幺蛾子 —— 要么消费者线程假死,要么中断后逻辑紊乱。
👉 王二的 “残次品” 代码(有坑)

package cn.tcmeta.locksupport;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.LockSupport;
/**
* @author: laoren
* @description: 王二的坑:LockSupport实战没处理细节,线程假死+中断逻辑乱
* @version: 1.0.0
*/
public class LockSupportProducerConsumerBad {
// 仓库(最多存5个商品)
private static final AtomicInteger STORE = new AtomicInteger(0);
private static final int MAX_CAPACITY = 5;
static void main() throws InterruptedException {
// 生产者线程
Thread producer = new Thread(() -> {
while (true) {
if (STORE.get() < MAX_CAPACITY) {
// 生产商品
STORE.incrementAndGet();
System.out.println("生产者:生产1个商品,库存=" + STORE.get());
// 唤醒消费者
LockSupport.unpark(consumer); // 坑1:消费者还没初始化就unpark?
} else {
// 库存满了,生产者阻塞
System.out.println("生产者:库存满了,等着...");
LockSupport.park(); // 坑2:没处理中断,也没判断库存,容易假死
}
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}, "Producer");
// 消费者线程(这里有问题:producer里先用到了consumer)
Thread consumer = new Thread(() -> {
while (true) {
if (STORE.get() > 0) {
// 消费商品
STORE.decrementAndGet();
System.out.println("消费者:消费1个商品,库存=" + STORE.get());
// 唤醒生产者
LockSupport.unpark(producer);
} else {
// 库存空了,消费者阻塞
System.out.println("消费者:库存空了,等着...");
LockSupport.park();
}
try {
TimeUnit.MILLISECONDS.sleep(150);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}, "Consumer");
// 启动线程
producer.start();
consumer.start();
// 主线程等5秒后中断生产者(测试中断逻辑)
TimeUnit.SECONDS.sleep(5);
producer.interrupt();
System.out.println("主线程:中断生产者线程");
}
}
}
运行结果:要么空指针,要么假死
王二挠头:“我明明唤醒了啊,怎么还假死?有时候还报空指针?”
哇哥指着代码里的坑,像老中医号脉似的精准:“两个大雷:
- 线程引用提前使用:生产者里先调用LockSupport.unpark(consumer),但 consumer 线程还没初始化,直接空指针 —— 就像你喊 “3 号病人”,但 3 号还没挂号;
- park 后没重检条件:生产者因为库存满而 park,被唤醒后没再检查库存,直接生产 —— 万一唤醒的时候库存还是满的,就会超卖;
- 中断没处理:生产者被中断后,park 唤醒了,但没退出循环,还在死循环生产 —— 就像病人被打扰醒了,还赖在椅子上不走。”
王二挠头:“我明明唤醒了啊,怎么还假死?有时候还报空指针?”
二、用 “快递收发站” 讲透实战核心:唤醒后必须 “验票”

哇哥拿小区的快递收发站类比,王二一下就懂了:
“生产者是快递员,消费者是取件人,仓库是快递架(最多放 5 个快递)。之前的问题在哪?快递员(生产者)看到架子满了,往地上一坐就等(park),取件人(消费者)取走一个快递,喊一声‘快来’(unpark),快递员不管架子满不满,直接就放快递 —— 这能不出问题吗?”
“那该咋弄?” 王二问。
“唤醒后必须‘验票’—— 也就是重新检查条件,” 哇哥说,“快递员被喊醒后,先看看架子是不是真的有空位,再放快递;取件人被喊醒后,先看看架子上是不是真的有快递,再取。这就是 LockSupport 实战的核心:park 前要检查条件,被唤醒后还要再检查一遍。”
➡️ 实战优化原则(王二记在小本本上)
实战优化原则(王二记在小本本上)
- 线程引用安全:确保 unpark 时,目标线程已经初始化完成 —— 别喊还没挂号的病人;
- 条件循环检查:用while循环代替if判断条件,被唤醒后重新检查 —— 醒了先看看情况,再干活;
- 中断优雅处理:park 唤醒后,判断线程中断状态,必要时退出循环 —— 被打扰了就别硬赖着;
- 避免重复唤醒:不用频繁 unpark,确保唤醒有意义 —— 别没事就喊病人,打扰人家休息。
哥帮王二改了代码,加了条件循环、中断处理、线程引用安全,跑了一下午都没出问题:
package cn.tcmeta.locksupport;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.LockSupport;
// 优化版:LockSupport实战无坑,生产者消费者稳如老狗
public class LockSupportProducerConsumerGood {
// 仓库(原子类保证线程安全)
private static final AtomicInteger STORE = new AtomicInteger(0);
private static final int MAX_CAPACITY = 5;
public static void main(String[] args) throws InterruptedException {
// 1. 先初始化线程引用(解决空指针问题)
Thread producer = null;
Thread consumer = null;
// 生产者线程
Thread finalConsumer = consumer;
producer = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) { // 检查中断,优雅退出
// 2. 条件循环检查:库存满了就park(唤醒后再检查一遍)
while (STORE.get() >= MAX_CAPACITY) {
System.out.println("生产者:库存满了(" + STORE.get() + "),等着...");
java.util.concurrent.locks.LockSupport.park(); // 阻塞
// 3. 唤醒后检查中断,要是被中断了就退出
if (Thread.currentThread().isInterrupted()) {
System.out.println("生产者:被中断了,退出生产");
return;
}
}
// 生产商品
STORE.incrementAndGet();
System.out.println("生产者:生产1个,库存=" + STORE.get());
// 唤醒消费者(确保消费者已初始化)
if (finalConsumer != null) {
LockSupport.unpark(finalConsumer);
}
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
// 捕获中断,设置中断标志,循环会退出
Thread.currentThread().interrupt();
}
}
}, "Producer");
// 消费者线程
Thread finalProducer = producer;
consumer = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
// 2. 条件循环检查:库存空了就park
while (STORE.get() <= 0) {
System.out.println("消费者:库存空了(" + STORE.get() + "),等着...");
LockSupport.park();
// 3. 唤醒后检查中断
if (Thread.currentThread().isInterrupted()) {
System.out.println("消费者:被中断了,退出消费");
return;
}
}
// 消费商品
STORE.decrementAndGet();
System.out.println("消费者:消费1个,库存=" + STORE.get());
// 唤醒生产者
LockSupport.unpark(finalProducer);
try {
TimeUnit.MILLISECONDS.sleep(150);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}, "Consumer");
// 启动线程
producer.start();
consumer.start();
// 主线程等5秒后中断生产者和消费者
TimeUnit.SECONDS.sleep(5);
producer.interrupt();
consumer.interrupt();
System.out.println("主线程:中断生产者和消费者");
}
}

王二盯着日志,感慨道:“原来关键是用 while 循环检查条件,唤醒后再看一遍库存,之前用 if 就掉坑里了。”
“这就是实战和 demo 的区别,” 哇哥说,“demo 里怎么写都没事,生产环境里,线程被唤醒的原因有很多 —— 可能是 unpark,可能是中断,可能是虚假唤醒,所以必须重新检查条件。”
四、深入源码:LockSupport 是 AQS 的 “核心武器”
哇哥突然话锋一转:“你知道 ReentrantLock、CountDownLatch 这些 JUC 工具类,底层是怎么实现线程阻塞唤醒的吗?全是 LockSupport!”
他打开 JDK 源码,找到 AQS的awaitNanos方法:

“你看,AQS 的核心就是个线程等待队列,” 哇哥指着源码,“当线程竞争锁失败时,AQS 就把线程放进队列,然后调用 LockSupport.park () 阻塞它;当锁释放时,AQS 就把队列头的线程取出来,调用 LockSupport.unpark () 唤醒它 ——LockSupport 就是 AQS 的‘核心武器’。

“简单说,AQS 是‘线程管理员’,负责排队和调度;LockSupport 是‘执行员’,负责实际的阻塞和唤醒,” 哇哥总结,“懂了 LockSupport,你看 AQS 源码就像看白话文,不然全是天书。”
五、LockSupport 实战场景扩展:线程中断的 “优雅处理”

王二又问:“之前用 park 处理中断,中断标志是 true,要是我想在中断后继续执行,咋办?”
“用Thread.interrupted()清除中断标志,” 哇哥写了个例子,“这个方法会返回中断状态,然后把标志清掉 —— 就像病人被打扰醒了,护士说‘没事,你继续等’,病人就把‘被打扰’的记录擦掉,接着等。”
package cn.tcmeta.locksupport;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
/**
* @author: laoren
* @description: // LockSupport处理中断后,继续执行
* @version: 1.0.0
*/
public class LockSupportInterruptContinue {
public static void main(String[] args) throws InterruptedException {
Thread worker = new Thread(() -> {
int count = 0;
while (count < 5) {
System.out.println("工人线程:第" + (count + 1) + "次等待...");
// 使用带超时的 park 防止永久阻塞
LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(10));
// 检查中断,清除中断标志
if (Thread.interrupted()) {
System.out.println("工人线程:被中断了,但我偏要继续干!");
// 中断标志已被清除,下一次循环还能正常执行
}
count++;
try {
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException e) {
System.err.println("睡眠过程中收到中断信号");
Thread.currentThread().interrupt(); // 保持中断状态
}
}
System.out.println("工人线程:活干完了,下班!");
}, "Worker-Thread");
worker.start();
// 主线程中断工人线程两次
TimeUnit.SECONDS.sleep(1);
worker.interrupt();
System.out.println("主线程:第一次中断工人线程");
TimeUnit.SECONDS.sleep(2);
worker.interrupt();
System.out.println("主线程:第二次中断工人线程");
}
}
工人线程:第1次等待...
主线程:第一次中断工人线程
工人线程:被中断了,但我偏要继续干!
工人线程:第2次等待...
主线程:第二次中断工人线程
工人线程:被中断了,但我偏要继续干!
工人线程:第3次等待...
工人线程:第4次等待...
工人线程:第5次等待...
工人线程:活干完了,下班!
王二眼睛亮了:“这比 wait/notify 灵活多了,wait 被中断直接抛异常,还得用 try-catch 包着,LockSupport 想咋处理就咋处理。”
六、面试必问:LockSupport 实战与源码题(附答案)

哇哥整理了 3 道实战 + 源码的面试题,王二背完直呼 “稳了”:
👉 面试题 1:用 LockSupport 实现生产者消费者模式,关键要注意什么?
答案:
核心是 “条件循环检查 + 中断处理 + 线程安全”,三点缺一不可:
- 条件循环:用while代替if检查库存(或其他条件),避免唤醒后条件不满足导致逻辑错误;
- 中断处理:park 唤醒后检查Thread.currentThread().isInterrupted(),必要时优雅退出,别死循环;
- 线程安全:确保 unpark 时目标线程已初始化,共享资源用原子类或锁保护;
- 避免无效唤醒:只在条件变化时 unpark(比如生产后唤醒消费者,消费后唤醒生产者),别频繁唤醒。
⚠️ 面试题 2:LockSupport 的 park () 会响应哪些唤醒场景?
答案:
park () 会在 4 种场景下唤醒,实战中要全考虑到:
- 其他线程调用LockSupport.unpark(当前线程);
- 其他线程中断当前线程(thread.interrupt());
- 虚假唤醒(虽然概率极低,但 JVM 可能会莫名唤醒线程,所以必须用 while 循环检查条件);
- 调用LockSupport.park(Object blocker)后,blocker 被修改(极少用)。
➡️ 面试题 3:AQS 为什么要用 LockSupport,而不用 wait/notify?
答案:
因为 LockSupport 解决了 wait/notify 的三大痛点,适配 AQS 的线程调度需求:
- 无需同步块:AQS 的线程队列管理不需要 synchronized,LockSupport 不用同步块,契合 AQS 的设计;
- 精准唤醒:AQS 需要唤醒队列中特定的线程(比如头节点的下一个线程),LockSupport 的 unpark 能精准唤醒,而 notify 是随机的;
- 唤醒优先于等待:AQS 中可能先把线程放进队列,再释放锁唤醒它,LockSupport 的 “先 unpark 再 park” 支持这种场景,wait/notify 不行。
七、总结:LockSupport 实战封神心法(王二刻在键盘上)

王二把实战和源码的核心编成顺口溜,贴满了显示器:
- LockSupport 实战强,生产消费稳当当;
- while 循环查条件,if 判断易上当;
- park 唤醒查中断,优雅退出别硬扛;
- AQS 底层全靠它,阻塞唤醒不慌张;
- 四种场景能唤醒,实战里面全记详。
✔️ 哇哥的终极彩蛋
“最后送你个面试大招,” 哇哥压低声音,“面试时被问 LockSupport,你先写生产者消费者代码,再打开 AQS 的awaitNanos等方法,说‘你看,JDK 源码都这么用’—— 面试官一准觉得你是深入源码的高手,不是只会用 API 的菜鸟。”
王二点了点头,把优化后的生产者消费者代码部署到测试环境,跑了 24 小时,线程状态稳定,没有一次假死 —— 他终于不再是那个只会背 API 的菜鸟了。


760

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



