【问题标题】:Java deadlock questionJava死锁问题
【发布时间】:2011-03-28 04:28:19
【问题描述】:

谁能解释一下为什么这段代码会出现死锁。谢谢

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

【问题讨论】:

标签: java


【解决方案1】:

考虑以下几点:

  • Thread1 run() { alphonse.bow(gaston); }
  • Thread2 run() { gaston.bow(alphonse); }
  • Thread1 进入alphonse.bow(gaston);,锁定alphonse,因为bow()synchronized
  • Thread2 进入gaston.bow(alphonse);,锁定gaston,因为bow()synchronized
  • Thread1 中,bower.bowBack(this); 的计算结果为 gaston.bowBack(alphonse);
    • Thread1 尝试获取当前由 Thread2 持有的gaston 的锁
  • Thread2 中,bower.bowBack(this); 的计算结果为 alphonse.bowBack(gaston);
    • Thread2 尝试获取当前由 Thread1 持有的alphonse 的锁
  • 每个线程都在等待另一个线程释放锁,因此死锁

问题是当前有过多的synchronized。有很多方法可以“解决”这个问题;这是一个有启发性的解决方案:

    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,使用synchronized(this) 语句。这样可以防止死锁。

这里引用Effective Java 2nd Edition,Item 67:避免过度同步

为避免活性和安全故障,永远不要在 synchronized 方法或块中将控制权交给客户端。换句话说,在synchronized 区域内,不要调用旨在被覆盖或由客户端以函数对象形式提供的方法。从classsynchronized 区域的角度来看,这些方法是外星人。该类不知道该方法的作用,也无法控制它。根据外星方法的作用,从synchronized 区域调用它可能会导致异常、死锁或数据损坏。

[...] 通常,您应该在synchronized 区域内做尽可能少的工作。获取锁,检查共享数据,必要时对其进行转换,然后解除锁。

本质上,bower.bowBack(this) 是试图将控制权让给 alien 方法,因为bowBack() 不是final 中的class Friend 方法。考虑以下解决问题的尝试,例如:

    // 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()alien 方法,因此应该 NOTsynchronized 区域内调用。

参考文献

另见

  • 有效的 Java 第 2 版
    • 第 66 条:同步访问共享可变数据
    • 第 15 条:最小化可变性

【讨论】:

    【解决方案2】:

    这可能是如何执行的。

    1. 输入alphonse.bow(gaston);,由于synchronized关键字,alphonse现在被锁定
    2. 输入gaston.bow(alphonse);,gaston 现已锁定
    3. 无法从第一个bow 方法调用执行bower.bowBack(this);,因为gaston (bower) 已锁定。等待锁被释放。
    4. 无法从第二个 bow 方法调用执行 bower.bowBack(this);,因为 alphonse (bower) 已锁定。等待锁被释放。

    两个线程互相等待释放锁。

    【讨论】:

      【解决方案3】:

      最好的理解方法是在调用 bower.bowBack 之前将代码放在 bow() 中

      try {
      Thread.sleep(1);
      } catch (InterruptedException e) {
      e.printStackTrace();
      }
      

      【讨论】:

      • 在 bow() 方法中调用 bower.bowBack() 方法之前
      猜你喜欢
      • 1970-01-01
      • 2016-01-28
      • 1970-01-01
      • 2012-04-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-12-06
      • 2013-07-02
      相关资源
      最近更新 更多