8.多线程--避免活跃性危险
一.死锁
经典的“哲学家进餐”问题很好地描述了死锁状况。5个哲学家去吃中餐,坐在-张圆桌旁。他们有5根筷子(而不是5双),并且每两个人中间放一根筷子。哲学家们时而思考,时而进餐。每个人都需要- -双筷子才能吃到东西,并在吃完后将筷子放回原处继续思考。有些筷子管理算法能够使每个人都能相对及时地吃到东西(例如一个饥饿的哲学家会尝试获得两根邻近的筷子,但如果其中一根正在被另一个哲学家使用,那么他将放弃已经得到的那根筷子,并等待几分钟之后再次尝试),但有些算法却可能导致一些或者所有哲学家都“饿死”(每个人都立即抓住自己左边的筷子,然后等待自己右边的筷子空出来,但同时又不放下已经拿到的筷子)。后一种情况将产生死锁:每个人都拥有其他人需要的资源,同时又等待其他人已经拥有的资源,并且每个人在获得所有需要的资源之前都不会放弃已经拥有的资源。
1.锁顺序死锁
下面程序中的LeftRightDeadlock存在死锁风险。leftRight 和rightLeft这两个方法分别获得left锁和right锁。如果- 一个线程调用了leftRight, 而另-一个线程调用了rightLeft, 并且这两个线程的操作是交错执行,如下图所示,那么它们会发生死锁。
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() { } }
2.动态的锁顺序死锁
有时候,并不能清楚地知道是否在锁顺序上有足够的控制权来避免死锁的发生。考虑下面程序中看似无害的代码,它将资金从-一个账户转入另-一个账户。在开始转账之前,首先要获得这两个Account对象的锁,以确保通过原子方式来更新两个账户中的余额,同时又不破坏- -些不变性条件,例如“账户的余额不能为负数”。
public class DynamicOrderDeadlock { // Warning: deadlock-prone! 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); } } } } }
在transferMoney中如何发生死锁?所有的线程似乎都是按照相同的顺序来获得锁,但事实上锁的顺序取决于传递给transferMoney的参数顺序,而这些参数顺序又取决于外部输人。如果两个线程同时调用transferMoney,其中-一个线程从X向Y转账,另一个线程从Y向X转账,那 么就会发生死锁。
3.在协作对象之间发生的死锁
4.开放调用
5.资源死锁
二.死锁的避免与诊断
1.支持定时的锁
三.其他活跃性危险
1.饥饿