Java死锁问题

Jon*_*ony 11 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();
    }
}
Run Code Online (Sandbox Code Playgroud)

pol*_*nts 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尝试获取gaston当前由Thread2持有的
  • Thread2中,bower.bowBack(this);求值为alphonse.bowBack(gaston);
    • 线程2尝试获取锁alphonse,目前持有线程1
  • 每个线程都在等待另一个释放锁,从而导致死锁

问题是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());
    }
Run Code Online (Sandbox Code Playgroud)

现在bowBack()完全synchronized,但bow()只是synchronized部分,使用synchronized(this)语句.这样可以防止死锁.

以下是Effective Java 2nd Edition的引用,第67项:避免过度同步

为避免活动和安全故障,请勿synchronized方法或块中将控制权交给客户.换句话说,在synchronized区域内,不要调用设计为被覆盖的方法,或者由客户端以函数对象的形式提供的方法.从classsynchronized地区的角度来看,这种方法是陌生的.该类不知道该方法的作用,也无法控制它.根据外来方法的作用,从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());
    }
Run Code Online (Sandbox Code Playgroud)

上面的代码不会与当前alphonse/gaston场景死锁,但是由于bow()将控制权转移到非final方法bowBack(),因此子类可以@Override以导致bow()死锁的方式使用该方法.即,bowBack()是一种外来到方法bow(),因此应当NOT已经从内调用synchronized区域.

参考

也可以看看

  • 有效的Java第二版
    • 项目66:同步对共享可变数据的访问
    • 第15项:尽量减少可变性


Nik*_*bak 13

这是它可能会被执行的方式.

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

两个线程都互相等待释放锁定.