A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

本帖最后由 不二晨 于 2018-7-3 09:37 编辑

百川东到海,何日复西归?少壮不努力,老大徒伤悲。———《汉乐府·长歌行》
河水东流不复返就如时间的流逝不可能停留,告诫我们年少时如果不珍惜时间努力向上,到老只能白白地悔恨自己与悲伤了。

上文讲解了锁顺序死锁发生的情况,但是还存在一些死锁的情况,不是顺序锁那么简单可以被发现。因为有可能并不是在同一个方法中显示请求两个锁,而是嵌套另一个方法去获取第二个锁。

我们举个例子来说明,下面代码清单姑且当做是表示一个出租车系统吧,Taxi 表示出租车,具有位置和方向两个属性;Dispatcher表示一组出租车。

import java.awt.Point;import java.util.HashSet;import java.util.Set;public class ThreadDeadLockTest{   
class Taxi{        
private Point location, destation;     
   private final Dispatcher dispatcher;        
public Taxi(Dispatcher dispatcher) {            
this.dispatcher = dispatcher;        }        
//获取同步锁      
public synchronized Point getLocation() {            
return location;        }        
//获取Taxi同步锁,同时嵌套的方法还有获取Dispatcher的同步锁        
public synchronized void setLocation(Point location) {            
this.location = location;            
if(location.equals(destation)) {               
dispatcher.notifyAvailable(this);            }        
}    }    class Dispatcher
{        private final Set<Taxi> taxis;      
private final Set<Taxi> availableTaxis;      
public Dispatcher() {           
taxis = new HashSet<Taxi>();           
availableTaxis = new HashSet<Taxi>();      
}        
//获取同步锁      
public synchronized void notifyAvailable(Taxi taxi) {           
availableTaxis.add(taxi);        
}        
//获取同步锁,同时还会获取Taxi的同步锁      
public synchronized Image getImage() {            
Image image = new Image();            
for(Taxi t : taxis) {               
image.drawer(t.getLocation());           
}            return image;      
}   
}}

从上面代码可以看出,setLocation 和 getImage 方法都会获取两个锁。假如一个线程调用了 setLocation 方法,那么首先会获取到一个 Taxi 的锁,然后嵌套调用了 notifyAvailable,或获取到 Dispatcher 的锁。同样,另外一个线程调用了 getImage 方法,首先他会获取 Dispatcher 的锁,然后获取 Taxi 的锁。所以两个线程会以不同的顺序占用锁,那么就会产生死锁的情况。

线程A --> Taxi的锁 --> Dispatcher 的锁线程B --> Dispatcher的锁 --> Taxi 的锁
  • 1
  • 2

对于上面这种会发生死锁的情况,往往是比较难察觉的。这告诉我们,在持有锁的时候,调用外部方法可能会获取其他锁或者遭遇严重的超时阻塞,由于你不知道外部方法的情况,这通常很难进行分析。

当调用的方法不需要持有锁时,这被称为开放调用,它有着更好的行为。如何理解这句话?让我们来重构上面的代码,使其变成开放调用,从而避免死锁问题。我们需要减少 synchronize 的使用,只守护那些调用共享状态的操作。如下代码清单所示:

import java.awt.Point;import java.util.HashSet;import java.util.Set;public class ThreadDeadLockTest{   
class Taxi{      
private Point location, destation;        
private final Dispatcher dispatcher;      
public Taxi(Dispatcher dispatcher) {            
this.dispatcher = dispatcher;      
}      
//获取同步锁        
public synchronized Point getLocation() {           
return location;      
}        
//缩小锁的范围,不会同时去获取两个锁        
public void setLocation(Point location) {            
boolean isReached = false;           
//缩小锁的范围            
synchronized (this) {               
this.location = location;               
isReached = location.equals(destation);            }            
if(isReached)
{               
dispatcher.notifyAvailable(this);            }
       }
   }   
class Dispatcher{      
private final Set<Taxi> taxis;      
  private final Set<Taxi> availableTaxis;      
public Dispatcher() {           
taxis = new HashSet<Taxi>();           
availableTaxis = new HashSet<Taxi>();      
}        
//获取同步锁        
public synchronized void notifyAvailable(Taxi taxi) {           
availableTaxis.add(taxi);        
}      
//缩小锁的范围,不会同时去获取两个锁        
public Image getImage() {           
Set<Taxi> copy;           
//缩小锁的范围            
synchronized(this) {               
copy = new HashSet<Taxi>(taxis);           
}           
Image image = new Image();           
for(Taxi t : copy) {               
image.drawer(t.getLocation());           
}           
return image;      
}   
}}

getImage 和 setLocation 都缩小了锁的范围,获取第二锁之前已经释放了第一个锁,所以不会产生顺序死锁问题了。但是可能的情况是,可能会使得一个原子操作变为非原子操作。有时候这样的损失是可以接受的。比如这里 copy 的时候,可能不是瞬时最新的值。

如果一个程序一次之多获取一个锁,那么就不会产生锁顺序死锁。所以,尽量减少潜在锁之间的交互数量,遵守并文档化该锁顺序协议,缺一不可。

本文完结。


【转载】原文地址: https://blog.csdn.net/amd123456789/article/details/80864822


2 个回复

倒序浏览
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马