Java死锁问题

时间:2010-08-11 04:35:07

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

3 个答案:

答案 0 :(得分:25)

请考虑以下事项:

  • 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的引用,第67项:避免过度同步

  

为了避免活跃和安全失败,永远不会在synchronized方法或块中放弃对客户端的控制。换句话说,在synchronized区域内,不要调用设计为被覆盖的方法,或者由客户端以函数对象的形式提供的方法。从classsynchronized区域的角度来看,此类方法是 alien 。该类不知道该方法的作用,也无法控制它。根据外来方法的作用,从synchronized区域调用它可能会导致异常,死锁或数据损坏。

     

[...]通常,您应该在synchronized区域内尽可能少地完成工作。获取锁,检查共享数据,必要时进行转换,然后放下锁。

实质上,bower.bowBack(this)试图将控制权交给外来方法,因为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(),因此子类可以{{1这种方法会导致@Override死锁。也就是说,bow()bowBack()异类方法,因此应该从bow()区域内调用 NOT

参考

另见

  • Effective Java 2nd Edition
    • 项目66:同步对共享可变数据的访问
    • 第15项:最小化可变性

答案 1 :(得分:13)

以下是它可能会被执行的方式。

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

答案 2 :(得分:1)

最好的理解方法是在调用bower.bowBack

之前将其放在bow()中的代码下面
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}