死锁是抱着不放,活锁是互相谦让到天荒地老!两个线程都在运行,但都无法继续执行。
一、什么是活锁?
死锁 vs 活锁
死锁: 线程阻塞,互相等待
// 线程A持有锁1,等待锁2
// 线程B持有锁2,等待锁1
// 两个线程都BLOCKED
活锁: 线程运行,但无进展
// 线程A释放资源,等待B
// 线程B释放资源,等待A
// 两个线程都RUNNABLE,但互相谦让
生活类比
死锁像两辆车在窄路相遇🚗🚙:
- 谁都不让,卡死
活锁像两个人在走廊相遇🚶🚶:
- 都想让路
- 同时左移 → 同时右移 → 永远相遇
- 一直在动,但都过不去
二、活锁示例代码
示例1:资源谦让
public class LiveLockDemo {
static class Spoon {
private Diner owner;
public Spoon(Diner owner) {
this.owner = owner;
}
public synchronized void use() {
System.out.println(owner.name + " 用勺子吃饭");
}
public synchronized void setOwner(Diner d) {
owner = d;
}
public synchronized Diner getOwner() {
return owner;
}
}
static class Diner {
private String name;
private boolean isHungry;
public Diner(String name) {
this.name = name;
this.isHungry = true;
}
public void eatWith(Spoon spoon, Diner spouse) {
while (isHungry) {
// 如果勺子不是自己的
if (spoon.getOwner() != this) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
continue;
}
continue;
}
// 如果配偶也饿了,把勺子让给对方
if (spouse.isHungry) {
System.out.println(name + ": 你先吃吧亲爱的");
spoon.setOwner(spouse); // 谦让
continue; // 继续循环
}
// 自己吃
spoon.use();
isHungry = false;
spoon.setOwner(spouse);
}
}
}
public static void main(String[] args) {
Diner husband = new Diner("丈夫");
Diner wife = new Diner("妻子");
Spoon spoon = new Spoon(husband);
new Thread(() -> husband.eatWith(spoon, wife)).start();
new Thread(() -> wife.eatWith(spoon, husband)).start();
// 输出:
// 丈夫: 你先吃吧亲爱的
// 妻子: 你先吃吧亲爱的
// 丈夫: 你先吃吧亲爱的
// ... 永远循环,活锁!
}
}
三、活锁的三种解决方案
方案1:随机退避⭐推荐
public void eatWith(Spoon spoon, Diner spouse) {
while (isHungry) {
if (spoon.getOwner() != this) {
Thread.sleep(1);
continue;
}
if (spouse.isHungry) {
System.out.println(name + ": 你先吃吧");
spoon.setOwner(spouse);
// ✅ 随机等待一段时间
int waitTime = ThreadLocalRandom.current().nextInt(1, 10);
Thread.sleep(waitTime);
continue;
}
spoon.use();
isHungry = false;
}
}
原理: 随机等待打破对称性
方案2:优先级调整
static class Diner {
private int priority; // 优先级
public void eatWith(Spoon spoon, Diner spouse) {
while (isHungry) {
if (spoon.getOwner() != this) {
Thread.sleep(1);
continue;
}
// ✅ 优先级低的让给优先级高的
if (spouse.isHungry && spouse.priority > this.priority) {
System.out.println(name + ": 你优先级高,你先吃");
spoon.setOwner(spouse);
continue;
}
spoon.use();
isHungry = false;
}
}
}
方案3:资源排序
public class ResourceOrdering {
public void transfer(Account from, Account to, int amount) {
// ✅ 按账号ID排序,避免循环等待
Account first = from.id < to.id ? from : to;
Account second = from.id < to.id ? to : from;
synchronized (first) {
synchronized (second) {
from.balance -= amount;
to.balance += amount;
}
}
}
}
四、活锁检测
方法1:监控线程状态
public class LiveLockDetector {
private final Map<Long, ThreadInfo> threadInfos = new ConcurrentHashMap<>();
public void detect() {
ThreadMXBean bean = ManagementFactory.getThreadMXBean();
for (ThreadInfo info : bean.dumpAllThreads(false, false)) {
if (info.getThreadState() == Thread.State.RUNNABLE) {
// 检查栈帧是否一直重复
ThreadInfo oldInfo = threadInfos.get(info.getThreadId());
if (oldInfo != null) {
if (isSameStackTrace(oldInfo, info)) {
System.out.println("可能的活锁: " + info.getThreadName());
}
}
threadInfos.put(info.getThreadId(), info);
}
}
}
private boolean isSameStackTrace(ThreadInfo old, ThreadInfo current) {
StackTraceElement[] oldStack = old.getStackTrace();
StackTraceElement[] currentStack = current.getStackTrace();
if (oldStack.length != currentStack.length) {
return false;
}
for (int i = 0; i < oldStack.length; i++) {
if (!oldStack[i].equals(currentStack[i])) {
return false;
}
}
return true;
}
}
方法2:超时检测
public class TimeoutDetector {
public boolean tryWithTimeout(Runnable task, long timeout, TimeUnit unit) {
Future<?> future = executor.submit(task);
try {
future.get(timeout, unit);
return true;
} catch (TimeoutException e) {
System.out.println("任务超时,可能活锁");
future.cancel(true);
return false;
}
}
}
五、实战:转账防活锁
public class BankAccount {
private final long id;
private int balance;
public void transfer(BankAccount target, int amount) {
// 方案1:资源排序
BankAccount first = this.id < target.id ? this : target;
BankAccount second = this.id < target.id ? target : this;
synchronized (first) {
synchronized (second) {
if (this.balance >= amount) {
this.balance -= amount;
target.balance += amount;
}
}
}
}
// 方案2:超时机制
public boolean transferWithTimeout(BankAccount target, int amount, long timeout) {
long deadline = System.currentTimeMillis() + timeout;
while (System.currentTimeMillis() < deadline) {
if (tryTransfer(target, amount)) {
return true;
}
// 随机退避
try {
Thread.sleep(ThreadLocalRandom.current().nextInt(1, 10));
} catch (InterruptedException e) {
return false;
}
}
return false;
}
private boolean tryTransfer(BankAccount target, int amount) {
if (Thread.holdsLock(this) || Thread.holdsLock(target)) {
return false;
}
synchronized (this) {
if (synchronized (target)) {
if (this.balance >= amount) {
this.balance -= amount;
target.balance += amount;
return true;
}
}
}
return false;
}
}
六、预防活锁checklist✅
□ 引入随机性(随机退避)
□ 设置优先级(避免对称)
□ 资源排序(统一顺序)
□ 超时机制(避免永久等待)
□ 监控线程状态(检测异常)
□ 避免无限重试(设置重试上限)
七、面试高频问答💯
Q: 死锁和活锁的区别?
A:
- 死锁:线程BLOCKED,互相等待
- 活锁:线程RUNNABLE,互相谦让
- 饥饿:线程RUNNABLE,得不到资源
Q: 如何避免活锁?
A:
- 随机退避:打破对称性
- 优先级:避免无休止谦让
- 资源排序:统一获取顺序
- 超时:避免永久等待
Q: 如何检测活锁?
A:
- 监控线程状态(一直RUNNABLE但无进展)
- 检查栈帧重复
- 设置超时告警
下一篇→ 并发三性:可见性、原子性、有序性🔺