【问题标题】:How does synchronized work in JavaJava 中同步是如何工作的
【发布时间】:2010-10-19 11:26:17
【问题描述】:

首先,here's a sample

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

我不明白如何阻塞发生。 main 函数启动两个线程,每个线程都开始自己的弓。

“同步”究竟会阻止什么?为同一个对象运行相同的函数(正如我最初认为的那样)?同一类的所有对象都具有相同的功能?同一个对象的所有同步函数?同一类的所有对象的所有同步函数?

在这里帮帮我。

【问题讨论】:

标签: java multithreading deadlock


【解决方案1】:

在 Java 中,每个 Object 都为线程提供了synchronize 或锁定的能力。当一个方法被同步时,该方法使用它的对象实例作为锁。在您的示例中,方法bowbowBack 都是synchronized,并且都属于同一类Friend。这意味着执行这些方法的任何线程都将在 Friend 实例上同步作为其锁。

会导致死锁的一系列事件是:

  1. 第一个线程开始调用alphonse.bow(gaston),这是alphonseFriend对象上的synchronized。这意味着线程必须从这个对象获取锁。
  2. 第二个线程开始调用gaston.bow(alphonse),这是gastonFriend对象上的synchronized。这意味着线程必须从这个对象获取锁。
  3. 现在启动的第一个线程调用bowback 并等待释放gaston 上的锁。
  4. 现在启动的第二个线程调用bowback 并等待释放alphonse 上的锁。

更详细地显示事件的顺序:

  1. main() 开始在主 Therad(称为线程 #1)中执行,创建两个 Friend 实例。到目前为止,一切顺利。
  2. 主线程使用代码new Thread(new Runnable() { ... 启动它的第一个新线程(称为线程#2)。线程#2 调用alphonse.bow(gaston),即synchronized 上的alphonse Friend 对象。线程#2 因而获得alphonse 对象的“锁”并进入bow 方法。
  3. 此处出现时间片,原始线程有机会进行更多处理。
  4. 主线程启动第二个新线程(称为线程#3),就像第一个线程一样。线程#3 调用gaston.bow(alphonse),它在gaston Friend 对象上同步。由于尚未有人为gaston 对象实例获取“锁”,因此线程#3 成功获取此锁并进入bow 方法。
  5. 此处出现时间片,线程 #2 有机会进行更多处理。
  6. 线程#2 现在调用bower.bowBack(this);bower 是对gaston 实例的引用。这是gaston.bowBack(alphonse) 调用的逻辑等价物。因此,此方法是 synchronized 实例上的 synchronized。此对象的锁已被获取并由另一个线程(线程#3)持有。因此,线程#2 必须等待gaston 上的锁被释放。线程进入等待状态,允许线程 #3 继续执行。
  7. 线程#3 现在调用bowback,在这种情况下,它在逻辑上与调用alphonse.bowBack(gaston) 相同。为此,它需要获取alphonse 实例的锁,但该锁由线程#2 持有。该线程现在进入等待状态。

您现在处于两个线程都无法执行的位置。线程#2 和线程#3 都在等待释放锁。但是如果没有线程取得进展,就不能释放任何锁。但是如果不释放锁,两个线程都无法取得进展。

因此:死锁!

死锁通常取决于发生的特定事件序列,这会使调试变得困难,因为它们难以重现。

【讨论】:

  • 哦,好的。所以锁属于整个对象。我不知道为什么我认为它只是对被阻塞的给定对象调用相同的同步方法。我想这回答了我的问题。
  • 对象不执行同步/锁定,而是线程。
  • 其实我特意讲了线程获取锁,但是我看到我的语言有些不准确。我会说得更清楚。
  • 这可能已经晚了,但我认为问题是 Java 是如何获得这些“锁”的?以线程安全的方式执行此操作的底层实现是什么?
【解决方案2】:

同步方法与将所有这些方法代码包含在一个

中相同
synchronized(this) {
  /// code here ...
}

阻止。

对于给定的对象实例o,一次只有一个线程可以运行任何synchronized(o) 块。其他所有尝试执行此操作的线程都会哭泣,直到运行该块的线程(具有同步锁)退出该块(放弃锁)。

在您的情况下,当 Alphonse 开始在线程 1 中鞠躬时发生死锁,从而进入同步块。然后线程 1 被系统换出,因此线程 2 可以启动,并让 Gaston 鞠躬。但是 Gaston 还不能退缩,因为它正在 Alphonse 上进行同步,并且 Thread 1 已经拥有那个锁。因此它将等待线程 1 离开该块。然后系统将换回线程 1,这将尝试让 Alphonse 回弓。除非它不能这样做,因为线程 2 在 Gaston 上具有同步锁。两个线程现在都卡住了,等待对方完成鞠躬后才能鞠躬......

【讨论】:

    【解决方案3】:

    同一个对象的所有同步函数。将方法标记为“已同步”与在方法的全部内容周围放置“已同步 (this) {”块非常相似。我不说“相同”的原因是因为我不知道编译器是否发出相同的字节码,但是 AFAIK 定义的运行时效果是相同的。

    死锁是一种经典的锁反转。一个线程锁定 alphonse。然后(或同时在多核系统上)另一个线程锁定gaston。这部分要求线程的调度恰好在正确的点交错。

    然后每个线程(以任何顺序或同时)尝试获取已由另一个线程持有的锁,因此每个线程都进入睡眠状态。在对方释放其锁之前,两者都不会唤醒,但在唤醒(或终止)之前,两者都不会释放其锁。

    【讨论】:

      【解决方案4】:

      Synchronized has two effects:

      • 首先,对同一对象的同步方法的两次调用不可能交错。当一个线程正在为一个对象执行同步方法时,所有其他为同一对象调用同步方法的线程都会阻塞(暂停执行),直到第一个线程处理完该对象。
      • 其次,当一个同步方法退出时,它会自动与同一对象的任何后续同步方法调用建立起之前的关系。这保证了对对象状态的更改对所有线程都是可见的。

      简而言之,它会阻止对同一对象的同步方法的任何调用。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-04-23
        • 2020-09-01
        • 1970-01-01
        • 2019-08-31
        相关资源
        最近更新 更多