第十章 避免活跃性危险
摘要: 本章主要介绍了许多可能造成活跃性降低的情况以及相关的危险操作。
10.1 死锁
说道死锁最典型的问题就是哲学家就餐的问题,如果五个哲学家就餐桌上有五支筷子,那么当五个哲学家同时拿起左手边的筷子时,他们就会一直持有一支筷子而等待另外的筷子从而造成死锁,在没有外界介入的情况下他们就将永久的等待下去。
那么除了上述典型的问题还有什么情况有可能导致死锁呢?
锁顺序死锁
public class LeftRightDeadlock {
private final Object left = new Object();
private final Object right = new Object();
public void leftRight() {
synchronized (left) {
synchronized (right) {
doSomething();
}
}
}
public void rightLeft() {
synchronized (right) {
synchronized (left) {
doSomethingElse();
}
}
}
void doSomething() {
}
void doSomethingElse() {
}
}
这样简单的代码会因为方法A和方法B的加锁顺序不同而导致死锁。当线程A锁住A的同时线程B锁住B 那么双方都在等待对方持有的资源而不释放自己手中的资源从而导致死锁。
动态锁顺序死锁
public static void transferMoney(Account fromAccount,
Account toAccount,
DollarAmount amount)
throws InsufficientFundsException {
synchronized (fromAccount) {
synchronized (toAccount) {
if (fromAccount.getBalance().compareTo(amount) < 0)
throw new InsufficientFundsException();
else {
fromAccount.debit(amount);
toAccount.credit(amount);
}
}
}
}
虽然不存在方法A、B的相互调用但是假设线程A和线程B在进行X转账给Y,Y转账给X的任务是就有可能发生死锁。
协作对象死锁
public class CooperatingDeadlock {
// Warning: deadlock-prone!
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(destination))
dispatcher.notifyAvailable(this);
}
public synchronized Point getDestination() {
return destination;
}
public synchronized void setDestination(Point destination) {
this.destination = destination;
}
}
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;
}
}
class Image {
public void drawMarker(Point p) {
}
}
}
对象Taxi和对象Dispatcher彼此并不知道会争夺锁。
10.2 死锁的避免与诊断
使用定时的锁持有是相对好的解决办法,使用JVM的线程转储功能对代码的加锁进行分析。
10.3 其他活跃性危险
除了上述代码层面的死锁以外,还有一些其他情况。
饥饿
当多个线程竞争资源时发生的死锁就是饥饿,最常见的引发饥饿的资源是CPU的时间周期,当高优先级的线程持有锁一段时间却又无法进行的时候,低优先级的线程就将无法得到资源。
活锁
他并不会导致线程停止,但是有可能会倒是线程死循环。在某些系统中,可能发生了某些异常导致事务回滚,但是因为其任务优先级高,所以会被放到队列首,当再次取任务时,就又会发生问题这种情况就是活锁。
总结: 本章着重介绍了对锁的错误使用可能带来的不良后果。以及如何去解决和分析,不过短短的一章也不可能说的很透彻,面面俱到。真正的解决死锁问题的方法还是类似于对于JVM底层调优这样东西,更多的是经验之谈。望诸君勤动手,勤动脑~