Java并发编程实战--死锁

来源:互联网 发布:巴特尔cba数据 编辑:程序博客网 时间:2024/04/18 11:55

今天看了第十章,描述了经典的“哲学家进餐”问题,Google 哲学家进餐 就有 ,不多解释。先解释一下锁:可以这么理解,每一个java对象都具有一个锁标记,而这个锁标记只能同时分配给一个线程,然后讲了死锁的产生原因:当一个线程永远地持有一个锁,而且其他线程都想要取得这个锁时,那么它们将永远被阻塞。下面用三种介绍三种“死锁”状况。

一.死锁

先看代码

[java] view plaincopy
  1. package 并发编程;  
  2.   
  3. public class LeftRightDeadLock {  
  4.     //注意:容易发生死锁  
  5.     private final Object left = new Object();  
  6.     private final Object right = new Object();  
  7.   
  8.     public void leftRight(){  
  9.         synchronized(left){  
  10.             synchronized(right){  
  11.                 doSomething();  
  12.             }  
  13.         }  
  14.     }  
  15.   
  16.     public void rightLeft(){  
  17.         synchronized(right){  
  18.             synchronized(left){  
  19.                 doSomethingElse();  
  20.             }  
  21.         }  
  22.     }  
  23.   
  24.     private void doSomethingElse() {  
  25.     }  
  26.       
  27.     private void doSomething() {  
  28.     }  
  29.   
  30. }  

上面的代码存在死锁的风险:leftRight()和rightLeft()分别获得left和right锁,如果A线程调用leftRight方法,而B线程调用rightLeft方法,而且他们是交错执行的,如下图,他们就有会出现死锁。


出现死锁的原因是:两个线程以不同顺序来获取相同的锁。如果以相同的顺序来获取锁,就不会出现循环的加锁依赖性,也就不会出现死锁。


二.动态的死锁

看代码

[java] view plaincopy
  1. package 并发编程;  
  2.   
  3. public class DynamicDeadLock {  
  4.     //注意:容易发生死锁 T.T  
  5.     public void transferMoney(Account from,Account to,DollarAmount num) throws Exception{  
  6.         synchronized(from){  
  7.             synchronized(to){  
  8.                 if(from.getBalance().compareTo(num) < 0){  
  9.                     throw new Exception();  
  10.                 }  
  11.                 from.debit(num);  
  12.                 to.credit(num);  
  13.             }  
  14.         }  
  15.     }  
  16. }  

上面说了:如果以相同的顺序来获取相同的锁,就不会出现循环的加锁依赖性,也就不会出现死锁。这个类中,只有一个方法,并且也是按照一个顺序来获取锁,应该不会有什么问题了吧?但实际上锁的获取是动态地取决于线程的行为,如果同时有A、B两个线程,A线程调用transferMoney(myBank,hisBank,1000),B线程调用transferMoney(myBank,hisBank,2000),实际上,A线程所谓的myBank其实是B线程的hisBank,A线程的hisBank其实是B线程的myBank,这样就导致两个线程获取锁的顺序不一样,产生了死锁。


三.协作对象间的死锁

先看两个类

[java] view plaincopy
  1. package 并发编程;  
  2. /** 
  3.  * 出租车类,可被出租车车队调用 
  4.  * @author Andre 
  5.  * 
  6.  */  
  7. public class Taxi {  
  8.       
  9.     private Point location,destination;//包含两个属性:当前位置和目标地  
  10.     private final Dispatcher dispatcher;//所属车队  
  11.       
  12.     public Taxi(Dispatcher dispatcher){  
  13.         this.dispatcher = dispatcher;  
  14.     }  
  15.       
  16.     public synchronized Point getLocation(){  
  17.         return location;  
  18.     }  
  19.       
  20.     /** 
  21.      * 通过GPS设置出租车位置 
  22.      * @param location 
  23.      */  
  24.     public synchronized void setLocation(Point location){  
  25.         this.location = location;  
  26.         if(location.equals(destination)){  
  27.             dispatcher.notifyAvailable(this);  
  28.         }  
  29.           
  30.     }  
  31.       
  32. }  

[java] view plaincopy
  1. package 并发编程;  
  2.   
  3. import java.awt.Image;  
  4. import java.util.Set;  
  5.   
  6. /** 
  7.  * 出租车车队类,指挥出租车的调动 
  8.  * @author Andre 
  9.  * 
  10.  */  
  11. public class Dispatcher {  
  12.     private final Set<Taxi> taxis;//出租车集合  
  13.     private final Set<Taxi> availableTaxis;///可用出租车集合  
  14.       
  15.     public Dispatcher(Set<Taxi> taxis , Set<Taxi> availableTaxis){  
  16.         this.taxis = taxis;  
  17.         this.availableTaxis = availableTaxis;  
  18.     }  
  19.   
  20.     public synchronized  void notifyAvailable(Taxi taxi) {  
  21.         availableTaxis.add(taxi);  
  22.     }  
  23.       
  24.     /** 
  25.      * 获得包含当前出租车的完整画面 
  26.      * @return 
  27.      */  
  28.     public synchronized CarImage getImage(){  
  29.         CarImage image = new CarImage();  
  30.         for(Taxi t: taxis)  
  31.             image.drawMarker(t.getLocation());  
  32.         return image;  
  33.     }  
  34.   
  35. }  
这两个类中,并没有显式地获取两个锁,但实际上,现在有一个Dispatcher对象和多个Taxi对象(即一个出租车车队和多辆出租车),A线程调用Taxi对象中的setLocation时,方法内部调用dispatcher.notifyAvailable(),该方法也加上了同步关键字synchronized,这样A线程就先后获取了Taxi对象和Dispatcher对象的锁。而同时B线程调用dispatcher.getImage(该方法有同步标记),而方法内部调用了t.getLocation也是一个同步方法,这样B线程就先后获取了Dispatcher对象和Taxi对象的锁。这样就回到了上面说过的危险情况:两个线程试图用不同顺序获取相同的锁。而这种情况也是更加难以被程序员发现出来的,因为它更加隐蔽。
原创粉丝点击