[Java并发编程实战]什么是锁顺序死锁

匿名 (未验证) 提交于 2019-12-02 21:53:52

精诚所至,金石为开。―――《后汉书・广陵思王荆传》
意思是人的诚心所到,能感动天地,使金石为之开裂。比喻只要专心诚意去做,什么疑难问题都能解决。

两个线程试图通过不同的顺序获取多个相同的锁。如果请求的顺序不相同,那么会出现循环的锁依赖现象,产生死锁。但是如果保证同时请求锁L和锁M的每一个线程,都是按照从 L 到 M 的顺序,那么就不会发生死锁了。

举个例子说明一下,让我们更加直观的了解顺序死锁问题,请看下面代码:

public class ThreadDeadLockTest{      private final Object left = new Object();     private final Object right = new Object();      public void leftRight() {         synchronized (left) {             synchronized (right) {                 doSomething();             }         }     }      public void rightLeft() {         synchronized (right) {             synchronized (left) {                 doSomething();             }         }     } }

如果一个线程调用了 leftRight, 另一个线程调用了 rightLeft,这样的交替运行,那么它们会发生死锁。线程 A 拿到了left锁,而线程 B 拿到了 right 锁,这个时候就陷入了相互等待的循环了。

解决的办法是,如果所有线程以通用的固定秩序获得锁,程序就不会出现锁顺序死锁问题了。

然而,有些时候并不是那么容易避免顺序死锁的发生。比如,下面这个代码:

public class ThreadDeadLockTest{      public void transferMoney(Account fromAccount, Account toAccount, Amount money) {          synchronized (fromAccount) {             synchronized (toAccount) {                 if(fromAccount < money) {                     throw new InsufficientFundsException();                 } else {                     fromAccount.debit(money);                     toAccount.credit(money);                 }             }         }     } }

针对上面程序,如果两个线程同时调用transferMoney, 一个从 A 向 B 转账,另一个从 B 向 A 转账,那么就会发生死锁。如下代码调用所示:

    transferMoney(myAccount, yourAccount, 10);     transferMoney(yourAccount, myAccount, 20);

可以看出,锁的顺序就是参数的顺序,它超出了我们的控制。那么,有么有办法来制定一个锁的规范,保证锁的顺序是一致的呢?

答案是肯定的,现在我们制定一种锁的顺序,并在整个应用程序都遵循它。我们可以使用

System.identityHashCode

的方式,它返回 Object.hashCode, 唯一一个值。但是,我们还得想如何规避哈希值冲突产生的问题。哈希冲突,我们可以再引入一个锁,当哈希冲突的时候,必须获取这个锁,从而保证一次只有一个线程执行哈希冲突的情况。

请看下面代码,保证了锁顺序的一致,避免了死锁的发生。

public class ThreadTest{     //额外锁,针对哈希值冲突的情况     private static final Object tieLock = new Object();      public void transferMoney(Account fromAccount, Account toAccount, Amount money) {          class Helper{             public void transfer() {                 if(fromAccount < money) {                     throw new InsufficientFundsException();                 } else {                     fromAccount.debit(money);                     toAccount.credit(money);                 }             }         }         //或许哈希值         int fromHashCode = System.identityHashCode(fromAccount);         int toHashCode = System.identityHashCode(toAccount);          if(fromHashCode < toHashCode) {             synchronized (fromAccount) {                 synchronized (toAccount) {                     new Helper().transfer();                 }             }         } else if(fromHashCode > toHashCode) {             synchronized(toAccount) {                 synchronized(fromAccount) {                     new Helper().transfer();                 }             }         } else {             synchronized(tieLock) {                 synchronized(fromAccount) {                     synchronized(toAccount) {                         new Helper().transfer();                     }                 }             }         }     } }

上面程序就保证了锁顺序的一致性,从而规避了锁顺序死锁的情况。很多时候不易察觉这种动态的锁顺序,我们需要更加细心的使用多个锁,尽早的制定锁顺序规范。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!