Java死锁问题
有人能解释一下为什么这段代码会死锁吗?谢谢Java死锁问题,java,Java,有人能解释一下为什么这段代码会死锁吗?谢谢 public class Deadlock { static class Friend { private final String name; public Friend(String name) { this.name = name; } public String getName() { return this.name;
public class Deadlock {
static class Friend {
private final String name;
public Friend(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public synchronized void bow(Friend bower) {
System.out.format("%s: %s has bowed to me!%n",
this.name, bower.getName());
bower.bowBack(this);
}
public synchronized void bowBack(Friend bower) {
System.out.format("%s: %s has bowed back to me!%n",
this.name, bower.getName());
}
}
public static void main(String[] args) {
final Friend alphonse = new Friend("Alphonse");
final Friend gaston = new Friend("Gaston");
new Thread(new Runnable() {
public void run() { alphonse.bow(gaston); }
}).start();
new Thread(new Runnable() {
public void run() { gaston.bow(alphonse); }
}).start();
}
}
下面是它可能的执行方式
alphonse.bow(加斯顿)代码>,由于已同步
关键字,alphonse现在已锁定
gaston.bow(阿尔方斯)代码>,加斯顿现在已锁定
bower.bowBack(此)代码>从第一个bow
方法调用开始,因为加斯顿(bower)已锁定。等待释放锁
bower.bowBack(此)代码>从第二个船头方法调用,因为阿尔方斯(船头)已锁定。等待释放锁
两个线程都等待对方释放锁。请考虑以下事项:
- 让Thread1
run(){alphonse.bow(gaston);}
- 让Thread2
run(){gaston.bow(alphonse);}
- 螺纹1进入阿尔方斯弓(加斯顿),锁定
,因为alphonse
是bow()
同步的
- 螺纹2进入加斯顿弓(阿尔方斯)代码>,锁定
,因为gaston
是bow()
同步的
- 在Thread1中,
bower.bowBack(这个)代码>计算为
gaston.bowBack(阿尔方斯)代码>
- Thread1尝试获取当前由Thread2持有的gaston的锁
- 在Thread2中,
bower.bowBack(这个)代码>评估为
阿尔方斯弓背(加斯顿)代码>
- Thread2尝试获取当前由Thread1持有的
的锁alphonse
- Thread2尝试获取当前由Thread1持有的
- 每个线程都在等待另一个线程释放锁,从而导致死锁
已同步。有很多方法可以“修复”这个问题;以下是一个有启发性的解决方案:
public void bow(Friend bower) {
synchronized (this) {
System.out.format("%s: %s has bowed to me!%n",
this.name, bower.getName());
}
bower.bowBack(this);
}
public synchronized void bowBack(Friend bower) {
System.out.format("%s: %s has bowed back to me!%n",
this.name, bower.getName());
}
现在,使用synchronized(this)
语句,bowBack()
已完全synchronized
,但bow()
仅部分已同步。这将防止僵局
这里引用了《有效Java第二版》第67项:避免过度同步
为避免活动性和安全性故障,切勿在同步的方法或块内将控制权让与客户端。换句话说,在synchronized
区域内,不要调用设计为重写的方法,也不要调用客户机以函数对象的形式提供的方法。从类
和synchronized
区域的角度来看,这些方法是不同的。这个类不知道这个方法做什么,也不能控制它。根据外来方法的功能,从synchronized
区域调用它可能会导致异常、死锁或数据损坏
[…]通常,您应该在同步的
区域内尽可能少做工作。获取锁,检查共享数据,必要时对其进行转换,然后放下锁
本质上,bower.bowBack(this)
是试图将控制权让给一个外来方法,因为bowBack()
不是类中的final
方法。考虑下面的尝试来解决这个问题,例如:
// attempt to fix: STILL BROKEN!!!
public synchronized void bow(Friend bower) {
System.out.format("%s: %s has bowed to me!%n",
this.name, bower.getName());
bower.bowBack(this);
// ceding control to alien method within synchronized block!
}
// not a final method, subclasses may @Override
public void bowBack(Friend bower) {
System.out.format("%s: %s has bowed back to me!%n",
this.name, bower.getName());
}
上述代码不会与当前的alphonse/gaston
场景死锁,但由于bow()
将控制权让给非最终的方法bowBack()
,子类可以@重写该方法,从而导致bow()
死锁。也就是说,bowBack()
是bow()
的外来方法,因此不应该从同步的区域中调用
工具书类
另见
- 有效Java第二版
- 项目66:同步访问共享可变数据
- 项目15:尽量减少可变性
最好的理解方法是在调用bower.bowBack之前将下面的代码放在bow()中
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
这并不总是会导致死锁。如果线程2在线程1完成后启动,那么就不会出现死锁。在调用bower.bowBack()方法之前,两个线程启动之间的Thread.sleep避免了in bow()方法的死锁