
本文探讨在并发场景中避免死锁的可靠策略,重点分析“重试获取锁”方案的缺陷,并介绍基于全局锁序的确定性解决方案,辅以可落地的 java 实现示例。
本文探讨在并发场景中避免死锁的可靠策略,重点分析“重试获取锁”方案的缺陷,并介绍基于全局锁序的确定性解决方案,辅以可落地的 java 实现示例。
在多线程编程中,swapValue(Data other) 这类需同时操作两个共享对象的方法极易引发死锁——尤其当线程 A 调用 a.swapValue(b) 而线程 B 同时调用 b.swapValue(a) 时,若未约定统一的加锁顺序,二者可能各自持有一把锁并无限等待对方释放,形成经典循环等待。
原问题中提出的“循环尝试 tryLock() + 主动 unlock()”方案(即所谓的“锁自旋重试”)虽能规避死锁,但存在明显缺陷:
- ❌ 非确定性与低效性:依赖竞争时机,线程可能长时间空转、频繁上下文切换,浪费 CPU;
- ❌ 逻辑脆弱:混用 synchronized 内置锁与显式 ReentrantLock,导致锁粒度与所有权混乱;
- ❌ 可维护性差:不符合“一次只申请一个资源”的设计直觉,难以推理和测试。
真正健壮的解法是 强制全局一致的锁获取顺序。核心思想是:对任意两个 Data 实例 this 和 other,始终按预定义的、与调用方向无关的唯一顺序加锁(例如先锁 ID 小者,再锁 ID 大者)。这样可彻底消除循环等待条件。
以下是推荐实现(使用 System.identityHashCode 的增强版,规避其非唯一性风险):
public class Data {
private final long id; // 全局唯一标识符,确保排序稳定
private long value;
private final Lock lock = new ReentrantLock();
public Data(long value) {
this.value = value;
// 使用 ThreadLocalRandom 避免 identityHashCode 碰撞,保证 id 唯一性
this.id = ThreadLocalRandom.current().nextLong();
}
public long getValue() {
lock.lock();
try {
return value;
} finally {
lock.unlock();
}
}
public void setValue(long value) {
lock.lock();
try {
this.value = value;
} finally {
lock.unlock();
}
}
public void swapValue(Data other) {
// 关键:按 id 升序确定加锁顺序,避免死锁
Data first = (this.id <= other.id) ? this : other;
Data second = (this.id <= other.id) ? other : this;
first.lock.lock();
try {
second.lock.lock();
try {
long temp = this.getValue();
long newValue = other.getValue();
this.setValue(newValue);
other.setValue(temp);
} finally {
second.lock.unlock(); // 逆序释放:先 second,后 first
}
} finally {
first.lock.unlock();
}
}
}✅ 关键优势说明:
- 确定性:无论 a.swapValue(b) 还是 b.swapValue(a),总按 min(id_a, id_b) → max(id_a, id_b) 顺序加锁;
- 高效性:无忙等、无重试开销,锁获取失败即阻塞(由 ReentrantLock 保障);
- 可扩展性:该模式可自然推广至 N 个对象的批量操作;
- 安全性:显式锁完全替代 synchronized,避免锁机制混用。
⚠️ 注意事项:
- id 必须在构造时一次性生成且不可变(如本例用随机长整型),禁用 System.identityHashCode() 直接作为排序依据;
- 加锁后务必使用 try-finally 确保解锁,即使发生异常也不遗漏;
- 解锁顺序建议与加锁顺序相反(LIFO),虽非死锁必要条件,但有助于资源释放的局部性与调试友好性;
- 若业务允许,更优方案是将 swapValue 改为静态方法 static void swap(Data a, Data b),由调用方明确传入参数,语义更清晰。
综上,“锁重试”是权宜之计,而基于唯一 ID 的锁序协议才是工业级并发编程中预防死锁的标准实践。它简洁、可靠、可验证,应成为处理多资源协同操作的默认范式。










