一、死锁
何为死锁,用一个进餐场景来描述,一桌子饭,5个人吃,5根筷子,每人面前一根,假如刚好有一种算法是每个人立即拿起一根筷子,同时又等待你右侧的人放下筷子,你成一双,然后吃饭,这样大家都会饿死,这就解释了死锁的现象。两个线程死锁也是同样的道理,线程A持有M锁,而等待L锁释放,线程B持有L锁,但等待M锁释放,于是就发生了死锁。
1.1、顺序死锁
public class LeftRightDeadlock {
private final Object left = new Object();
private final Object right = new Object();
public void leftRight() {
// 得到left锁
synchronized (left) {
// 得到right锁
synchronized (right) {
doSomething();
}
}
}
public void rightLeft() {
// 得到right锁
synchronized (right) {
// 得到left锁
synchronized (left) {
doSomethingElse();
}
}
}
}
- 线程A调用
leftRight()
方法,得到left锁 - 同时线程B调用
rightLeft()
方法,得到right锁 - 线程A和线程B都继续执行,此时线程A需要right锁才能继续往下执行。此时线程B需要left锁才能继续往下执行。
- 但是:线程A的left锁并没有释放,线程B的right锁也没有释放。
- 所以他们都只能等待,而这种等待是无期限的-->永久等待-->死锁
1.2动态锁顺序死锁
public static void transferMoney(Account fromAccount,
Account toAccount,
DollarAmount amount)
throws InsufficientFundsException {
// 锁定汇账账户
synchronized (fromAccount) {
// 锁定来账账户
synchronized (toAccount) {
// 判余额是否大于0
if (fromAccount.getBalance().compareTo(amount) < 0) {
throw new InsufficientFundsException();
} else {
// 汇账账户减钱
fromAccount.debit(amount);
// 来账账户增钱
toAccount.credit(amount);
}
}
}
- 如果两个线程同时调用
transferMoney()
- 线程A从X账户向Y账户转账
- 线程B从账户Y向账户X转账
- 那么就会发生死锁。
-
A:transferMoney(myAccount,yourAccount,10); B:transferMoney(yourAccount,myAccount,20);
1.3协作对象之间发生死锁
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;
}
// setLocation 需要Taxi内置锁
public synchronized void setLocation(Point location) {
this.location = location;
if (location.equals(destination))
// 调用notifyAvailable()需要Dispatcher内置锁
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);
}
// 调用getImage()需要Dispatcher内置锁
public synchronized Image getImage() {
Image image = new Image();
for (Taxi t : taxis)
// 调用getLocation()需要Taxi内置锁
image.drawMarker(t.getLocation());
return image;
}
}
class Image {
public void drawMarker(Point p) {
}
}
}
- 并且在操作途中是没有释放锁的
这就是隐式获取两个锁(对象之间协作)..
这种方式也很容易就造成死锁.....
二、避免死锁的方法
- 固定加锁的顺序(针对锁顺序死锁)
- 开放调用(针对对象之间协作造成的死锁)
- 使用定时锁-->
tryLock()
2.4死锁检测
JDK提供了两种方式来给我们检测:
- JconsoleJDK自带的图形化界面工具,使用JDK给我们的的工具JConsole
- Jstack是JDK自带的命令行工具,主要用于线程Dump分析。
- 具体参考:死锁检测
发生死锁的原因主要由于:
- 线程之间交错执行
- 解决:以固定的顺序加锁
- 执行某方法时就需要持有锁,且不释放
- 解决:缩减同步代码块范围,最好仅操作共享变量时才加锁
- 永久等待
- 解决:使用
tryLock()
定时锁,超过时限则返回错误信息
- 解决:使用