《Java并发编程》之七:避免活跃性危险
如果所有线程以固定顺序来获取锁,那么在程序中就不会出现锁顺序死锁问题。
通过锁顺序来避免死锁:
public class InduceLockOrder {
private static final Object tieLock = new Object();
public void transferMoney(final Account fromAcct,
final Account toAcct,
final DollarAmount amount)
throws InsufficientFundsException {
class Helper {
public void transfer() throws InsufficientFundsException {
if (fromAcct.getBalance().compareTo(amount) < 0)
throw new InsufficientFundsException();
else {
fromAcct.debit(amount);
toAcct.credit(amount);
}
}
}
int fromHash = System.identityHashCode(fromAcct);
int toHash = System.identityHashCode(toAcct);
if (fromHash < toHash) {
synchronized (fromAcct) {
synchronized (toAcct) {
new Helper().transfer();
}
}
} else if (fromHash > toHash) {
synchronized (toAcct) {
synchronized (fromAcct) {
new Helper().transfer();
}
}
} else {
synchronized (tieLock) {
synchronized (fromAcct) {
synchronized (toAcct) {
new Helper().transfer();
}
}
}
}
}
interface DollarAmount extends Comparable<DollarAmount> {
}
interface Account {
void debit(DollarAmount d);
void credit(DollarAmount d);
DollarAmount getBalance();
int getAcctNo();
}
class InsufficientFundsException extends Exception {
}
}
10.1.3 在协作对象之间发生的死锁
如果在持有锁时调用某个外部方法,那么将出现活跃性问题。在这个外部方法中可能会获取其他锁,或者阻塞时间过长,导致其他线程无法及时获得当前被持有的锁。
如果在调用某个方法的时候不需要持有锁,那么这种调用被称为开放调用Open Call ,依赖于开放调用的类通常能表现出更好的行为,也更易于编写。
class CooperatingNoDeadlock {
@ThreadSafe
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) {
boolean reachedDestination;
synchronized (this) {
this.location = location;
reachedDestination = location.equals(destination);
}
if (reachedDestination)
dispatcher.notifyAvailable(this);
}
public synchronized Point getDestination() {
return destination;
}
public synchronized void setDestination(Point destination) {
this.destination = destination;
}
}
@ThreadSafe
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 : copy)
image.drawMarker(t.getLocation());
return image;
}
}
class Image {
public void drawMarker(Point p) {
}
}
}
在程序中尽量使用开放调用。与那些在持有锁的时候调用外部方法程序相比,更易于对依赖于开放调用的程序进行死锁分析。
10.2 死锁的避免与诊断
尽可能使用开放调用,这能极大简化死锁分析过程。
支持定时的锁:显示使用Lock类中的定时tryLock功能来代替内置锁机制。这项技术只有在同时获取两个锁时才有效,如果在嵌套方法调用中请求多个锁,还是不行。
10.3 其他活跃性问题
饥饿:
当线程由于无法访问它所需要的资源而不能继续执行时,就发生饥饿starvation。最常见的资源就是CUP时钟周期,比如对线程的优先级使用不当就会出现这种情况。
要避免使用java的线程优先级,因为这会增加平台依赖性,并可能导致活跃性问题。
糟糕的响应性:
如果一个线程长时间占有一个锁,而其他想要访问这个容器的线程就必须等待很长时间。
活锁:
Livelock经常发生在处理事务消息的应用程序中,如果不能成功处理某个消息,那么消息处理机制将回滚整个事务,并将它重新放到队列开头,然后循环往复的执行,又把它放到开头。
通过在重试机制中引入随机性。可以有效的避免活锁的发生。
小结:活跃性故障时一个非常严重的问题,因为当出现活跃性故障时,除了终止应用程序之外没有其他任何机制可以帮助从这种故障恢复过来。最常见的活跃性故障就是锁顺序死锁。在设计的时候应该避免产生锁顺序死锁,确保线程在获取多个锁时采用一致顺序,同时防止嵌套调用锁,最好使用开放调用。
本人博客已搬家,新地址为:http://yidao620c.github.io/
推荐阅读