【问题标题】:Question About Deadlock Situation in Java关于Java死锁情况的问题
【发布时间】:2010-12-06 11:16:43
【问题描述】:

我正在学习 Java 中的死锁,有来自 Sun 官方教程的示例代码:

Alphonse 和 Gaston 是朋友,并且 礼尚往来的大信徒。一个严格的 礼貌规则是当你鞠躬时 对朋友,你必须保持低头 直到你的朋友有机会 返回弓。不幸的是,这 规则不考虑 两个朋友可能会鞠躬 同时彼此。

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

这是 Sun 的解释:

当死锁运行时,它非常 可能两个线程都会阻塞 当他们试图调用 bowBack。 两个区块都不会结束,因为 每个线程都在等待另一个 退出弓。

我似乎不太了解。当 alphonse.bow(gaston) 运行时, bow 方法被锁定。所以现在它会首先打印“Gaston has bowed to me!”。然后它会继续调用 bowBack,并锁定 bowBack。这怎么会导致死锁?我是否误解了调用同步方法时会发生什么?

如果有人能给我一个简单的解释,谢谢。

【问题讨论】:

  • 看起来这里有很多正确的答案,但我认为将响应格式化为 2 个单独的列可能更有帮助,每个列代表一个不同的线程。这将真正证明可以同时运行 2 组指令。
  • 我也有类似的困惑。感谢您的帖子和答案。

标签: java concurrency deadlock


【解决方案1】:

需要注意的重要一点是,被锁定的不是方法,而是对象实例

当您调用alphonse.bow(gaston) 时,它会尝试获取alphonse 上的锁。获得锁后,它会打印一条消息,然后调用gaston.bowBack(alphonse)。此时,它尝试获取gaston 上的锁。获得锁后,它会打印一条消息,然后释放锁,最后释放alphonse 上的锁。

在死锁中,锁的获取顺序使得任何一个线程都无法继续进行。

  • 线程 1:获取锁定 alphonse
  • 线程 2:获取锁定 gaston
  • 线程 1:打印消息
  • 线程 1:尝试获取 gaston 上的锁定 - 不能,因为线程 2 已经拥有它。
  • 线程 2:打印消息
  • 线程 2:尝试获取 alphonse 上的锁定 - 不能,因为线程 1 已经拥有它。

【讨论】:

  • 啊。好的,我想我明白了。所以当你在一个方法前面加上 synchronized 关键字时,就意味着当一个线程调用这个方法时,整个对象“锁定”了,这意味着没有其他线程可以调用这个对象上的任何其他方法,即使那些 OTHER 方法只是简单地打印“你好”?
  • 不完全。没有线程可以调用此对象上的任何其他同步方法,也没有线程可以进入在此对象实例上同步的 synchronized { } 块。
  • 例如即使其他线程拥有 alphonse 上的锁,其他线程也可以调用 alphonse.toString() 或 alphonse.getName()。
  • 打印消息有助于延迟线程,从而更有可能发生死锁。
【解决方案2】:

alphonsegaston 是两个不同的对象。每个对象都有一个与之关联的内在监视器(锁)。

可能会这样:

alphonse 已创建。他的对象监视器是 1。

加斯顿被创建。他的对象监视器是 2。

alphonse.bow(加斯顿); alphonse 现在拥有锁 #1

gaston.bow(alphonse); 加斯顿现在拥有锁#2

alphonse 在 gaston 上调用 bowBack 并等待锁定 #2 gaston 在 alphonse 上调用 bowBack 并等待锁定 #1

有意义吗?使用实例在方法期间监控的同步关键字锁。示例可以改写如下:

public class Deadlock {
    static class Friend {
        private final String name;
        public Friend(String name) {
            this.name = name;
        }
        public String getName() {
            return this.name;
        }
        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 void bowBack(Friend bower) {
            synchronized(this) {
                        System.out.format("%s: %s has bowed back to me!%n",
                    this.name, bower.getName());
            }
        }
    }
}

【讨论】:

  • 当 alphonse 调用 bowBack 时,它会传递自己的实例 (bowBack(this))。为什么 bowBack 需要加斯顿的锁?
  • 它没有,它只是用于记录目的。它可以很容易地传递friend.getName() 而不是整个Friend 对象。
【解决方案3】:

锁被保存在 Java 对象上,而不是 Java 方法上。因此,当在方法上使用同步时,它会锁定“this”对象。在静态方法的情况下,它锁定类对象。

您可以使用synchronized ( object ) { }显式指定监控对象

【讨论】:

    【解决方案4】:

    添加到 simonn 等人,

    如果您想对此进行可视化,请编译并运行程序并生成正在运行的程序的线程转储。您可以通过在 Windows 控制台中键入 Ctrl-Break 或向 *nix 系统发出 kill -QUIT [pid] 命令来执行此操作。这将为您提供系统中运行的所有Threads 的列表,以及它们正在运行或等待的位置,以及线程锁定或等待锁定的监视器。

    如果您在其构造函数中更改线程名称,您将更容易在完整的线程转储中找到它们:

       new Thread(new Runnable() {
            public void run() { alphonse.bow(gaston); }
        }, "Alphonse").start();
        new Thread(new Runnable() {
            public void run() { gaston.bow(alphonse); }
        }, "Gaston").start();
    

    【讨论】:

      【解决方案5】:

      方法定义中的同步是在对象(this)本身上同步的简写。本质上,这意味着 bow() 和 bowBack() 不能在同一个 Friend 对象上相互调用。

      现在如果两个朋友都进入 bow(),他们都不能调用对方的 bowBack() 方法。

      【讨论】:

        【解决方案6】:

        我必须仔细检查,但我认为同步方法锁定了类对象,因此它锁定了同一类中的其他同步方法。

        我认为它会锁定类对象本身,因此它甚至会阻塞不同的实例。

        编辑添加:

        看看java language spec的这一部分

        每个弓法都抓取它自己的对象监视器。然后两者都尝试调用另一个对象的弓,并阻止等待另一个监视器。

        【讨论】:

        • 不,在非静态方法签名中同步只会锁定当前实例,而不是类对象。
        • 顺便说一句,在这种情况下,锁定类对象实际上可以防止死锁!
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-03-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多