从零开始学多线程之死锁(八)

 

死锁

每个人手里都有其他人需要的资源,自己又不会放下手上的资源,这么一直等待下去,就会发生死锁.

当一个线程永远占有一个锁,而其他线程尝试去获得这个锁,那么它们将永远被阻塞.

当线程A占有锁L时,想要获得锁M,同时线程B持有M,并尝试得到L,两个线程将永远等待下去,这种情况是死锁最简单的形式(或称致命的拥抱,deadly embrace)

数据库不会发生死锁的情况,它会选择一个牺牲者,强行释放锁,让程序可以继续执行下去.

JVM不行,只能重启程序.

死锁并不会每次都出现

死锁很少能立即发现.一个类如果有发生死锁的潜在可能并不意味着每次都将发生,它只发生在该发生的时候.

当死锁出现的时候,往往是遇到了最不幸的时候--- 在高负载下.

锁顺序死锁

public class LeftRightDeadLock {     private Object leftLock = new Object();     private Object rightLock = new Object();       public void getLeftLock(){         synchronized (this.rightLock){             synchronized (this.leftLock){                 //do something             }         }     }       public void getRightLock(){         synchronized (this.leftLock){             synchronized (this.rightLock){                 //do something.             }         }     } }

两个线程分别进入getRightLock和getLeftLock方法,同时获得第一个锁,在等待下一个锁的时候,就会发生锁顺序死锁.

发生死锁的原因: 两个线程试图通过不同的顺序获得多个相同的锁.

如果请求的顺序相同就不会出现循环的锁依赖现象,就不会产生死锁了.

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

动态的锁顺序死锁

public class DynamicDeadLock {      public void transferMoney(Account fromAcount,Account toAccount){         synchronized (fromAcount){             synchronized (toAccount){                 //转账操作             }         }     } }

当两个线程同时调用transferMoney,一个从X向Y转账,另一个从Y向X转账,那就会发生死锁.

transferMoney(myAccount,yourAccount)  transferMoney(yourAccount,myAccount)

之前说了,造成死锁的原因就是以不同的顺序获得相同的锁.

那么要解决这个问题,我就就必须制定锁的顺序.

System.indentityHashCode(传入对象)方法可以得到对象的哈希码.我们通过哈希码来决定锁的顺序.

public class DynamicDeadLock {      private Object obj = new Object();       public void transferMoney(Account fromAcount,Account toAccount){     //这个内部类秒啊,可以减少重复代码         class Helper {             public void transferMoney(){                 //真正的转账操作..                 //假装使用 外部的两个参数 fromAcount和toAccount做一下操作..             }         }         //制定锁的顺序         int fromHash = System.identityHashCode(fromAcount);         int toHash = System.identityHashCode(toAccount);          if(fromHash<toHash){             synchronized (fromAcount){                 synchronized (toAccount){                     new Helper().transferMoney();                 }             }         }else if(fromHash>toHash){             synchronized (toAccount){                 synchronized (fromAcount){                     new Helper().transferMoney();                 }             }         }else{             //使用成员变量的锁             synchronized (obj){                 synchronized (fromAcount){                     synchronized (toAccount){                         new Helper().transferMoney();                     }                 }             }         }     } } 

虽然有点麻烦,但是减少了发生死锁的可能性.

注意上面代码的最后一种else的情况,使用了一个额外的obj的锁,这是因为极少数的情况下会出现hashcode相同的情况,当hashCode相同的时候,使用之前的两种顺序锁,两个线程同时调用两个方法,参数换位,颠倒顺序计算哈希值,就又有了出现死锁的可能,所以引入第三种锁来保证锁的顺序,从而减少死锁发生的可能性.

如果经常出现hash值冲突,那么并发性会降低(因为多加了一个锁),但是因为
System.identityHashCode的哈希冲突出现频率很低,所以这个技术以最小的代价,换来了最大的安全性.

如果Account具有一个唯一的,不可变的,并且具有可比性的key,比如账号,那么就可以通过账号来排定对象顺序,这样就能省去obj的锁了.

协作对象间的死锁

public class A {      private final B b ;      public A(B b) {         this.b = b;     }      public  synchronized  void methodA(){         //do something.          //调用B的同步的方法         b.methodB();      } }   public 

                    
                
50000+
5万行代码练就真实本领
17年
创办于2008年老牌培训机构
1000+
合作企业
98%
就业率

联系我们

电话咨询

0532-85025005

扫码添加微信