重复尝试获取锁能否有效防死锁?线程同步死锁优化方案咨询
线程同步与死锁预防:Data类swapValue方法的线程安全实现
问题背景
现有Data类持有一个长整型变量,多线程会调用其swapValue方法实现两个实例的值交换。原始代码如下:
public class Data { private long value; public Data(long value) { this.value = value; } public synchronized long getValue() { return value; } public synchronized void setValue(long value) { this.value = value; } public void swapValue(Data other) { long temp = getValue(); long newValue = other.getValue(); setValue(newValue); other.setValue(temp); } }
swapValue方法需要保证线程安全,且资源不可用时不能跳过值交换。直接在方法签名加synchronized会导致死锁(比如线程A调用dataA.swapValue(dataB),线程B调用dataB.swapValue(dataA),双方互相持有锁等待对方释放)。
我自己实现了一个解决方案,依赖线程释放资源后另一线程能在资源未被锁定时获取它的概率,代码如下:
private Lock lock = new ReentrantLock(); ... public void swapValue(Data other) { lock.lock(); while(!other.lock.tryLock()) { lock.unlock(); lock.lock(); } long temp = getValue(); long newValue = other.getValue(); setValue(newValue); other.setValue(temp); other.lock.unlock(); lock.unlock(); }
感觉这是个权宜之计,想请教:
- 这类方案是否常用?
- 是否存在行为更具确定性且实用的解决方案?
问题解答
你提出的方案是否常用?
这个方案本质是自旋+锁抢占的重试机制,属于乐观的死锁规避方式,但不属于工业级代码的常用方案:
- 依赖概率,极端高并发场景下可能出现长时间循环重试,浪费CPU资源;
lock.unlock()和lock.lock()之间存在间隙,当前线程会完全失去自身锁的持有权,可能被其他线程抢占,导致交换逻辑执行时机不可控;- 可读性差,维护成本高,其他开发者很难快速理解代码意图。
更具确定性的实用解决方案
1. 固定锁的获取顺序
死锁的核心原因是交叉锁获取,只要保证所有线程获取锁的顺序一致,就能从根源避免死锁。可以基于对象的唯一标识(或哈希值)确定锁的获取顺序:
public class Data { private long value; private final Lock lock = new ReentrantLock(); // 给每个Data实例分配唯一标识,避免哈希冲突 private final long id = System.nanoTime(); public Data(long value) { this.value = value; } 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 ? this : other; first.lock.lock(); try { second.lock.lock(); try { long temp = this.value; this.value = other.value; other.value = temp; } finally { second.lock.unlock(); } } finally { first.lock.unlock(); } } }
这种方式完全消除死锁可能,行为确定,是死锁预防的常用方案之一。
2. 使用带超时的tryLock显式控制
如果无法固定锁顺序,可以给tryLock添加超时时间,避免无限等待,同时加入短暂休眠减少CPU空转:
public void swapValue(Data other) throws InterruptedException { boolean acquiredSelf = false; boolean acquiredOther = false; try { while (!acquiredSelf || !acquiredOther) { acquiredSelf = lock.tryLock(100, TimeUnit.MILLISECONDS); if (acquiredSelf) { acquiredOther = other.lock.tryLock(100, TimeUnit.MILLISECONDS); if (!acquiredOther) { lock.unlock(); acquiredSelf = false; // 短暂休眠避免CPU空转 Thread.sleep(50); } } } // 执行交换逻辑 long temp = this.value; this.value = other.value; other.value = temp; } finally { if (acquiredOther) { other.lock.unlock(); } if (acquiredSelf) { lock.unlock(); } } }
这种方式通过超时机制避免无限阻塞,行为可控,适合无法固定锁顺序的场景。
3. 引入全局锁(简单直接但性能较低)
如果交换操作并发量不高,可以引入全局锁,所有交换操作串行执行:
public class Data { private static final Lock GLOBAL_SWAP_LOCK = new ReentrantLock(); private long value; private final Lock lock = new ReentrantLock(); // 构造方法、getValue、setValue实现同前 public void swapValue(Data other) { GLOBAL_SWAP_LOCK.lock(); try { this.lock.lock(); other.lock.lock(); try { long temp = this.value; this.value = other.value; other.value = temp; } finally { other.lock.unlock(); this.lock.unlock(); } } finally { GLOBAL_SWAP_LOCK.unlock(); } } }
这种方式实现最简单,但会降低并发性能,适合低并发场景。
内容的提问来源于stack exchange,提问作者Neran
相关产品推荐
相关产品推荐

