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 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(); } }
这可能是如何执行的。
- input
alphonse.bow(gaston);
,由于synchronized
关键字,alphonse现在被locking - input
gaston.bow(alphonse);
,gaston现在被locking - 无法执行
bower.bowBack(this);
从第一bow
方法调用,因为gaston(鲍尔)被locking。 等待locking被释放。 - 无法执行
bower.bowBack(this);
从第二bow
方法调用,因为阿尔方斯(鲍尔)被locking。 等待locking被释放。
两个线程都等待对方释放locking。
考虑以下几点:
- 让Thread1
run() { alphonse.bow(gaston); }
run() { alphonse.bow(gaston); }
- 让Thread2
run() { gaston.bow(alphonse); }
run() { gaston.bow(alphonse); }
-
alphonse.bow(gaston);
进入alphonse.bow(gaston);
,因为bow()
是synchronized
,所以锁住了alphonse
- Thread2进入
gaston.bow(alphonse);
,因为bow()
是synchronized
,所以lockinggaston
- 在
bower.bowBack(this);
,bower.bowBack(this);
评价为gaston.bowBack(alphonse);
- 线程1尝试获取当前由线程2保存的gaston的锁
- 在Thread2中 ,
bower.bowBack(this);
评估为alphonse.bowBack(gaston);
- 线程2尝试获取当前由Thread1保持的
alphonse
的锁
- 线程2尝试获取当前由Thread1保持的
- 每个线程正在等待另一个释放一个锁,从而导致死锁
问题是目前有太多的synchronized
。 有很多方法可以“解决”这个问题。 这是一个有益的解决scheme:
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()); }
现在, bowBack()
已经完全synchronized
,但是bow()
只能使用synchronized(this)
语句部分synchronized(this)
。 这将防止僵局。
以下是Effective Java 2nd Edition,Item 67中的引号:避免过度同步
为了避免生存和安全失败, 永远不要在
synchronized
方法或块内放弃对客户端的控制 。 换句话说,在synchronized
区域内,不要调用被devise为被重载的方法,或者以函数对象的forms提供给客户端。 从synchronized
地区的class
angular度来看,这种方法是陌生的 。 这个class级不知道这个方法是干什么的,也不能控制它。 取决于外来方法的作用,从synchronized
区域调用它可能会导致exception,死锁或数据损坏。[…]通常,你应该尽可能在
synchronized
区域内尽可能less的工作。 获取锁,检查共享数据,必要时进行转换,然后放下锁。
实质上, bower.bowBack(this)
是试图将控制权交给一个外来方法,因为bowBack()
不是class Friend
的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()
将控制权交给非final
方法bowBack()
,所以子类可以@Override
方法,这样会导致bow()
死锁。 也就是说, bowBack()
是一个bow()
的外部方法,因此不应该从synchronized
区域内调用。
参考
- JLS 8.4.3.6
synchronized
方法 - JLS 14.19
synchronized
声明 - JLS 17.1锁
也可以看看
- 有效的Java第二版
- 项目66:同步访问共享的可变数据
- 项目15:最小化可变性
在调用bower.bowBack之前,最好的方法是在bow()中放入代码
try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }