Java 死锁问题

2022-09-05 00:13:09

任何人都可以解释为什么这个代码中有一个死锁。谢谢

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

请考虑以下事项:

  • 线程 1 run() { alphonse.bow(gaston); }
  • 线程 2 run() { gaston.bow(alphonse); }
  • 线程 1 进入 ,锁定,因为 是alphonse.bow(gaston);alphonsebow()synchronized
  • 线程 2 进入 ,锁定,因为是gaston.bow(alphonse);gastonbow()synchronized
  • 线程 1 中,计算结果为bower.bowBack(this);gaston.bowBack(alphonse);
    • 线程 1 尝试获取 当前由 Thread2 持有的 锁gaston
  • Thread2 中,计算结果为bower.bowBack(this);alphonse.bowBack(gaston);
    • 线程 2 尝试获取 当前由 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()synchronizedbow()synchronizedsynchronized(this)

以下是《有效的 Java 第 2 版》第 67 项:避免过度同步中的引文

为避免活动和安全故障,切勿在方法或块内将控制权让渡给客户端。换句话说,在区域内,不要调用旨在重写或由客户端以函数对象形式提供的方法。从与地区的角度来看,这种方法是陌生的。该类不知道该方法的作用,也无法控制它。根据外来方法的作用,从区域调用它可能会导致异常、死锁或数据损坏。synchronizedsynchronizedclasssynchronizedsynchronized

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

实质上,是试图将控制权让渡给外星方法,因为 不是 中的方法。例如,请考虑以下尝试来修复此问题:bower.bowBack(this)bowBack()finalclass 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/gastonbow()finalbowBack()@Overridebow()bowBack()bow()synchronized

引用

另请参见

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

答案 2

以下是它可能如何执行。

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

两个线程都等待对方释放锁。


推荐