通过开放调用来避免死锁
一.概述
在编写并发程序的时候,我们经常会在一次业务逻辑处理过程中获取多个锁,然后做一些操作,如果这多个锁的获取顺序和其他地方的获取顺序不同的话,很容易发生死锁。为了避免这种死锁,我们必须保证获取多个锁的顺序一致,或者方法调用为开放调用,即调用某个方法时不需要持有锁。
二.示例分析
Taxi表示一个出租车对象,包含位置和目的地两个属性,Dispatcher代表一个出租车队。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | class Taxi {
@GuardedBy("this")private Point location, destination;
private final Dispatcher dispatcher;
public Taxi(Dispatcher dispatcher) {
this.dispatcher = dispatcher;
}
/** 获取出租车的位置 **/
public synchronized Point getLocation() {
return location;
}
/** 设置出租车的位置 **/
public synchronized void setLocation(Point location) {
this.location = location;
if (location.equals(distination)) {
dispatcher.notifyAvailable(this);
}
}
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | class Dispatcher {
@GuardedBy("this") private final Set<Taxi> taxis;
@GuardedBy("this") 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 synchronized Image getImage() {
Image image = new Image();
for (Taxi t : taxis) {
image.drawMarker(t.getLocation());
}
return image;
}
}
|
看看上面的代码,我们就知道setLocation和notifyAvailable方法都是同步方法,调用setLocation的线程首先获取Taxi上的锁,然后获取Dispatcher的锁。getImage方法先获取Dispatcher上的锁,再获取Taxi上的锁,这两个方法被不同的线程调用时容易产生死锁,相信现在的滴滴以及快的绝对不是这么干的。
下面我看看如何通过开放调用来避免这个死锁的问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | class Taxi {
@GuardedBy("this")private Point location, destination;
private final Dispatcher dispatcher;
public Taxi(Dispatcher dispatcher) {
this.dispatcher = dispatcher;
}
/** 获取出租车的位置 **/
public synchronized Point getLocation() {
return location;
}
/** 设置出租车的位置 **/
public void setLocation(Point location) {
boolean reachedDestination = false;
/** 缩小缩的范围 把方法变成开发调用 **/
synchronized(this) {
this.location = location;
reachedDestination = location.equals(distination);
}
if (reachedDestination) {
dispatcher.notifyAvailable(this);
}
}
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | class Dispatcher {
@GuardedBy("this") private final Set<Taxi> taxis;
@GuardedBy("this") 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 : taxis) {
image.drawMarker(t.getLocation());
}
return image;
}
}
|
通过上面的改造,我们把setLocation和getImage都变成了开放调用,与那些持有锁时调用外部方法的程序相比,更容易对依赖的开发调用的程序进行死锁分析。
在编写并发程序的时候,一定要注意一个思想,加锁范围最小化。