文章目录
📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌
📙 作者: 编程技术圈(哇哥面试陪跑)
👉 欢迎关注、分享、评论
✔️ 持续分享更多干货内容
🌐🌏🌎➕tcmeta, 欢迎沟通交流
📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌
零、引入
“面试官问‘活锁和饥饿咋解决?’,我张口就说死锁要固定锁顺序,结果面试官直接摇头,说我连概念都分不清……” 王二攥着面试反馈单,上面 “对并发问题理解片面” 的评语格外刺眼,眼看到手的大厂 offer 飞了,蹲在工位旁唉声叹气。
隔壁哇哥端着刚泡好的枸杞水,拍了拍他的肩膀:“你呀,就只懂死锁,活锁和饥饿比死锁更隐蔽,面试问的概率贼高!死锁是‘互不相让,卡死不动’,活锁是‘互相谦让,啥也干不成’,饥饿是‘强者通吃,弱者没机会’—— 今天我用食堂打饭的例子给你讲透,再带你写代码复现 + 修复,下次面试让你把面试官说懵。”
点赞 + 关注,跟着哇哥和王二,搞懂活锁、饥饿和死锁的区别,面试多拿 8k,工作中再也不踩这些隐蔽坑!

一、先分清仨概念:用食堂打饭讲明白(人话版)
哇哥先给王二画了张 “食堂场景对照表”,仨概念瞬间就懂了:

“死锁是‘不动’,活锁是‘白动’,饥饿是‘动不了’—— 这仨的核心区别,记死了!” 哇哥敲着桌子强调。
二、活锁:互相谦让的 “无效努力”,代码复现 + 修复

🎲 活锁的真实场景:转账代码的 “无限重试”
王二之前写过一个转账代码:两个用户互相转账,检测到余额不足就立即重试,结果两个线程一直检测、一直重试,永远转不成账 —— 典型的活锁。
✔️ 活锁代码复现(转账场景)
package cn.tcmeta.lock;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
* @author: laoren
* @description: // 活锁示例:两个用户互相转账,无限重试导致活锁
* @version: 1.0.0
*/
public class LiveLockSample {
// 用户类:包含余额和转账方法
static class User {
private final String name;
private int balance;
public User(String name, int balance) {
this.name = name;
this.balance = balance;
}
// 转账方法:余额不足就立即重试(活锁根源)
public boolean transfer(User target, int amount) {
// 检测余额是否足够
if (this.balance < amount) {
System.out.println(Thread.currentThread().getName() + ":" + this.name + "余额不足(" + this.balance + "),立即重试...");
return false;
}
// 余额足够,执行转账
this.balance -= amount;
target.balance += amount;
System.out.println(Thread.currentThread().getName() + ":" + this.name + "向" + target.name + "转账" + amount + "成功!");
System.out.println(this.name + "余额:" + this.balance + "," + target.name + "余额:" + target.balance);
return true;
}
public String getName() {
return name;
}
public int getBalance() {
return balance;
}
}
public static void main(String[] args) {
// 初始化两个用户:A有100元,B有100元
User A = new User("用户A", 100);
User B = new User("用户B", 100);
// 线程1:A向B转150元(余额不足,重试)
Runnable task1 = () -> {
while (!A.transfer(B, 150)) {
// 立即重试,没有延迟——活锁关键
}
};
// 线程2:B向A转150元(余额不足,重试)
Runnable task2 = () -> {
while (!B.transfer(A, 150)) {
// 立即重试,没有延迟
}
};
// 启动线程池执行任务
ExecutorService pool = Executors.newFixedThreadPool(2);
pool.submit(task1);
pool.submit(task2);
// 运行10秒后停止(避免无限运行)
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
pool.shutdownNow();
System.out.println("10秒后强制停止:任务完全没完成,活锁了!");
}
}
运行结果:无限重试,永远转不成账
pool-1-thread-1:用户A余额不足(100),立即重试...
pool-1-thread-2:用户B余额不足(100),立即重试...
pool-1-thread-1:用户A余额不足(100),立即重试...
pool-1-thread-2:用户B余额不足(100),立即重试...
...(无限循环)...
10秒后强制停止:任务完全没完成,活锁了!
🎲 活锁的核心原因:“无延迟重试” 导致同步谦让
哇哥解释:“两个线程都检测到余额不足,然后立即重试,相当于你和我在走廊里同时往左边让,又同时往右边让 —— 永远同步,永远办不成事。”
💯 活锁的解决方法:加 “随机重试延迟”,打破同步
给重试逻辑加随机的延迟,让两个线程的重试时间错开,就不会同步谦让了。

📌 修复后的代码(加随机延迟)
package cn.tcmeta.lock;
import lombok.Getter;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author: laoren
* @description: // 活锁修复:加随机重试延迟,打破同步
* @version: 1.0.0
*/
public class LiveLockFixed {
static class User {
@Getter
private final String name;
private final AtomicInteger balance;
private static final Random random = new Random();
public User(String name, int balance) {
this.name = name;
this.balance = new AtomicInteger(balance);
}
public boolean transfer(User target, int amount) {
// 原子性地完成整个转账操作
while (true) {
int currentBalance = balance.get();
if (currentBalance < amount) {
System.out.println(Thread.currentThread().getName() + ":" + this.name + "余额不足(" + currentBalance + "),延迟重试...");
try {
TimeUnit.MILLISECONDS.sleep(random.nextInt(400) + 100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false; // 中断则直接返回失败
}
continue; // 继续尝试
}
// CAS 操作确保转账过程的原子性
if (balance.compareAndSet(currentBalance, currentBalance - amount)) {
target.balance.addAndGet(amount);
System.out.println(Thread.currentThread().getName() + ":" + this.name + "向" + target.name + "转账" + amount + "成功!");
System.out.println(this.name + "余额:" + balance.get() + "," + target.name + "余额:" + target.balance.get());
return true;
}
// 如果CAS失败说明其他线程已经改变了余额,继续循环重试
}
}
public int getBalance() {
return balance.get();
}
}
public static void main(String[] args) {
User A = new User("用户A", 200); // 调整初始余额
User B = new User("用户B", 200);
Runnable task1 = () -> {
int retryCount = 0;
while (!A.transfer(B, 150) && retryCount++ < 10) {
if (Thread.currentThread().isInterrupted()) {
System.out.println("Task1 interrupted.");
break;
}
}
};
Runnable task2 = () -> {
int retryCount = 0;
while (!B.transfer(A, 100) && retryCount++ < 10) { // 改变转账金额避免死循环
if (Thread.currentThread().isInterrupted()) {
System.out.println("Task2 interrupted.");
break;
}
}
};
ExecutorService pool = Executors.newFixedThreadPool(2);
pool.submit(task1);
pool.submit(task2);
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
pool.shutdown();
try {
if (!pool.awaitTermination(1, TimeUnit.SECONDS)) {
pool.shutdownNow();
}
} catch (InterruptedException e) {
pool.shutdownNow();
}
}
System.out.println("任务完成,活锁被解决!");
System.out.println("最终结果:用户A余额=" + A.getBalance() + ", 用户B余额=" + B.getBalance());
}
}
运行结果:重试延迟错开,转账成功
pool-1-thread-2:用户B向用户A转账100成功!
pool-1-thread-1:用户A向用户B转账150成功!
用户B余额:250,用户A余额:150
用户A余额:150,用户B余额:250
任务完成,活锁被解决!
最终结果:用户A余额=150, 用户B余额=250
✅ 哇哥划重点:活锁的通用解决方法
- 加随机延迟:最常用,打破线程的同步重试节奏;
- 设置重试上限:避免无限重试,超过次数就降级(比如返回 “转账失败,请稍后再试”);
- 优先级调整:给线程设置不同优先级,让一个线程先执行,另一个后执行。

三、饥饿:强者通吃的 “资源垄断”,代码复现 + 修复
📌 饥饿的真实场景:线程池的 “非公平锁插队”
王二写的秒杀系统线程池,用了非公平锁的线程池,核心线程全被 VIP 用户的长任务占着,普通用户的短任务排了半小时还没执行 —— 典型的饥饿。

饥饿代码复现(线程池场景)
package cn.tcmeta.lock;
import java.util.concurrent.*;
/**
* @author: laoren
* @description: // 饥饿示例:非公平锁线程池,长任务垄断资源,短任务饿肚子
* @version: 1.0.0
*/
public class StarvationSample {
// VIP长任务:执行10秒,垄断线程
record VIPLongTask(int taskId) implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "执行VIP长任务" + taskId + "(耗时10秒)");
try {
TimeUnit.SECONDS.sleep(10); // 模拟长耗时
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println(Thread.currentThread().getName() + "完成VIP长任务" + taskId);
}
}
// 普通短任务:执行1秒,却抢不到资源
record NormalShortTask(int taskId) implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "执行普通短任务" + taskId + "(耗时1秒)");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println(Thread.currentThread().getName() + "完成普通短任务" + taskId);
}
}
public static void main(String[] args) {
// 线程池配置:核心2线程,最大2线程(非公平锁,默认)
ExecutorService pool = new ThreadPoolExecutor(
2, 2,
60, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10),
// 默认非公平锁的线程工厂,VIP任务优先提交,垄断资源
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy()
);
// 先提交2个VIP长任务,占满核心线程
pool.submit(new VIPLongTask(1));
pool.submit(new VIPLongTask(2));
// 再提交5个普通短任务,排队等待(饥饿)
for (int i = 1; i <= 5; i++) {
pool.submit(new NormalShortTask(i));
}
// 运行15秒后停止
try {
TimeUnit.SECONDS.sleep(15);
} catch (InterruptedException e) {
e.printStackTrace();
}
pool.shutdownNow();
System.out.println("15秒后停止:普通短任务只执行了极少数,大部分饿肚子!");
}
}
执行结果:
pool-1-thread-2执行VIP长任务2(耗时10秒)
pool-1-thread-1执行VIP长任务1(耗时10秒)
pool-1-thread-2完成VIP长任务2
pool-1-thread-1完成VIP长任务1
pool-1-thread-1执行普通短任务1(耗时1秒)
pool-1-thread-2执行普通短任务2(耗时1秒)
pool-1-thread-1完成普通短任务1
pool-1-thread-1执行普通短任务3(耗时1秒)
pool-1-thread-2完成普通短任务2
pool-1-thread-2执行普通短任务4(耗时1秒)
pool-1-thread-1完成普通短任务3
pool-1-thread-1执行普通短任务5(耗时1秒)
pool-1-thread-2完成普通短任务4
pool-1-thread-1完成普通短任务5
15秒后停止:普通短任务只执行了极少数,大部分饿肚子!
✔️ 饥饿的核心原因:“资源垄断”+“非公平竞争”
哇哥解释:“核心线程就 2 个,全被 VIP 长任务占了,普通短任务只能排队 —— 非公平锁下,新提交的 VIP 任务还可能插队,普通任务永远抢不到资源,这就是饥饿。”
‼️ 饥饿的解决方法:拆分线程池 + 公平锁
把长任务和短任务分开用不同的线程池,再给短任务线程池用公平锁,保证按顺序执行,不插队。
🎸 修复后的代码(拆分线程池 + 公平锁)
package cn.tcmeta.lock;
import java.util.concurrent.*;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author: laoren
* @description: // 饥饿修复:拆分线程池+公平锁,避免资源垄断
* @version: 1.0.0
*/
public class StarvationFixed {
// VIP长任务(不变)
record VIPLongTask(int taskId) implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "执行VIP长任务" + taskId + "(耗时10秒)");
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println(Thread.currentThread().getName() + "完成VIP长任务" + taskId);
}
}
// 普通短任务(不变)
record NormalShortTask(int taskId) implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "执行普通短任务" + taskId + "(耗时1秒)");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println(Thread.currentThread().getName() + "完成普通短任务" + taskId);
}
}
public static void main(String[] args) {
// 修复1:拆分线程池——VIP长任务专用线程池
ExecutorService vipPool = new ThreadPoolExecutor(
2, 2,
60, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(5),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy()
);
// 修复2:普通短任务专用线程池,用公平锁
ExecutorService normalPool = new ThreadPoolExecutor(
3, 3, // 多给几个核心线程
60, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10),
// 自定义线程工厂,用公平锁的ReentrantLock
r -> new Thread(r, "普通任务线程"),
new ThreadPoolExecutor.CallerRunsPolicy()
) {
// 重写newTaskFor,保证任务按顺序执行(公平)
@Override
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
return new FutureTask<>(runnable, value) {
// 公平锁保证任务按提交顺序执行
private final ReentrantLock lock = new ReentrantLock(true);
@Override
public void run() {
lock.lock();
try {
super.run();
} finally {
lock.unlock();
}
}
};
}
};
// 提交VIP长任务到专属线程池
vipPool.submit(new VIPLongTask(1));
vipPool.submit(new VIPLongTask(2));
// 提交普通短任务到专属线程池(不会被VIP任务垄断)
for (int i = 1; i <= 5; i++) {
normalPool.submit(new NormalShortTask(i));
}
// 运行15秒后停止
try {
TimeUnit.SECONDS.sleep(15);
} catch (InterruptedException e) {
e.printStackTrace();
}
vipPool.shutdownNow();
normalPool.shutdownNow();
System.out.println("15秒后停止:普通短任务全部执行完成,饥饿问题解决!");
}
}
执行结果:
普通任务线程执行普通短任务1(耗时1秒)
普通任务线程执行普通短任务2(耗时1秒)
pool-1-thread-1执行VIP长任务1(耗时10秒)
普通任务线程执行普通短任务3(耗时1秒)
pool-1-thread-2执行VIP长任务2(耗时10秒)
普通任务线程完成普通短任务3
普通任务线程完成普通短任务2
普通任务线程完成普通短任务1
普通任务线程执行普通短任务4(耗时1秒)
普通任务线程执行普通短任务5(耗时1秒)
普通任务线程完成普通短任务5
普通任务线程完成普通短任务4
pool-1-thread-2完成VIP长任务2
pool-1-thread-1完成VIP长任务1
15秒后停止:普通短任务全部执行完成,饥饿问题解决!
💯 哇哥划重点:饥饿的通用解决方法
- 拆分资源池:长任务 / 短任务、VIP / 普通任务分开用不同的线程池,避免资源垄断;
- 使用公平锁:保证线程按顺序获取资源,不插队;
- 设置资源上限:给长任务设置超时时间,避免长期占用资源;
- 优先级调整:给饥饿的线程提高优先级(慎用,可能引发新的饥饿)。
四、死锁、活锁、饥饿 终极对比表(面试直接抄)
王二掏出小本本,把哇哥的总结记成表格,面试时直接答:

五、总结:避坑心法,看完就能用

✅ 哇哥把核心要点缩成 3 句顺口溜,王二贴在显示器上:
- 死锁防顺序:多锁操作固定顺序,别互相抢;
- 活锁加延迟:重试逻辑别同步,加随机延迟;
- 饥饿分池子:长短短任务分开池,公平锁来兜底。
😭哇哥的血泪彩蛋

“我早年做支付系统,写了个无延迟重试的退款代码,” 哇哥捂脸,“结果线上出现活锁,退款请求无限重试,数据库被刷爆 —— 查了 6 小时才发现是活锁,加了随机延迟立马好。后来做秒杀,又因为线程池没拆分导致普通用户饥饿,被用户投诉到工信部,从那以后,我写并发代码必先查‘死锁、活锁、饥饿’三个坑!”
🗨️ 最后说句实在的

**死锁、活锁、饥饿是并发编程的 “三大隐形坑”,面试必问,工作中必踩。**记住:死锁是 “卡死”,活锁是 “白忙”,饥饿是 “饿肚子”,按场景用对应的解决方法,比瞎写代码靠谱 10 倍。
今天的代码你复制过去改改参数就能用,不管是转账系统、秒杀系统还是线程池,都能避开这些坑。如果帮你搞定了面试,点赞 + 分享给你那还在分不清仨概念的同事!



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



