Java双线程操作不同BCell对象执行swap方法可能引发什么问题?
分析BCell类swap方法的并发死锁问题
兄弟,你这个场景妥妥会触发死锁!我给你一步步拆解下发生的过程,你立马就能明白。
先把你的代码贴出来方便分析(顺便补了你漏写的getValue()括号):
class BCell { int value; public synchronized int getValue() { return value; } public synchronized void setValue(int i) { value=i; } public synchronized void swap(BCell x) { int temp = getValue(); setValue(x.getValue()); x.setValue(temp); } }
死锁发生的具体过程
当thread1执行p1.swap(p2)时:
- 因为
swap是synchronized方法,thread1会先获取p1对象的锁。 - 接着执行
getValue()——这个方法也是synchronized,但因为已经持有p1的锁,所以能顺利拿到当前value。 - 下一步调用
x.getValue()(也就是p2.getValue()),这时候thread1需要去获取p2对象的锁。
与此同时,thread2在执行p2.swap(p1):
- 同样,
thread2先获取p2对象的锁。 - 执行到
x.getValue()(也就是p1.getValue()),这时候它需要去获取p1对象的锁。
现在关键矛盾来了:
thread1拿着p1的锁,死死等着拿p2的锁;thread2拿着p2的锁,死死等着拿p1的锁。
两个线程都不肯释放自己手里的锁,也拿不到需要的锁,就这么无限卡住了——这就是典型的死锁,完全符合死锁的四大条件:互斥、占有且等待、不可抢占、循环等待。
怎么解决?
核心思路是:让所有线程在获取多个对象锁时,都遵循完全一致的顺序,这样就不会出现循环等待的情况。
比如我们可以根据对象的哈希值来确定锁的顺序,不管是p1.swap(p2)还是p2.swap(p1),都会先锁哈希值更小的那个对象,再锁更大的那个。修改后的swap方法如下:
public void swap(BCell x) { // 确定锁的顺序,避免循环等待 BCell first = this.hashCode() < x.hashCode() ? this : x; BCell second = this.hashCode() > x.hashCode() ? this : x; // 按顺序获取锁 synchronized (first) { synchronized (second) { int temp = getValue(); setValue(x.getValue()); x.setValue(temp); } } }
这里要提一句:如果两个对象的哈希值恰好相同(概率极低),还是可能有问题。这种极端情况可以额外加一个全局锁兜底,不过绝大多数业务场景下,哈希值排序的方式已经足够解决问题了。
另外,也可以用Java的Lock接口配合tryLock方法来尝试获取锁,要是获取失败就释放已持有的锁,不过实现起来会比顺序锁复杂一些,顺序锁是最直接的解决方案。
内容的提问来源于stack exchange,提问作者Ioannis Brant-Ioannidis
相关产品推荐
相关产品推荐

