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(); } } 

这可能是如何执行的。

  1. inputalphonse.bow(gaston); ,由于synchronized关键字,alphonse现在被locking
  2. inputgaston.bow(alphonse); ,gaston现在被locking
  3. 无法执行bower.bowBack(this); 从第一bow方法调用,因为gaston(鲍尔)被locking。 等待locking被释放。
  4. 无法执行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的锁
  • 每个线程正在等待另一个释放一个锁,从而导致死锁

问题是目前有太多的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地区的classangular度来看,这种方法是陌生的 。 这个class级不知道这个方法是干什么的,也不能控制它。 取决于外来方法的作用,从synchronized区域调用它可能会导致exception,死锁或数据损坏。

[…]通常,你应该尽可能在synchronized区域内尽可能less的工作。 获取锁,检查共享数据,必要时进行转换,然后放下锁。

实质上, bower.bowBack(this)是试图将控制权交给一个外来方法,因为bowBack()不是class Friendfinal方法。 考虑下面的尝试来解决这个问题,例如:

  // 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(); }