web-dev-qa-db-ja.com

デッドロック-この例ではどうなりますか?

誰でも説明できます:

  1. なぜデッドロックが発生するのですか?
  2. ガストンはどのようにアルフォンスがその機能を終了する前に機能の弓に入ることができますか? (関数bowBack()を終了するには、関数bow()から戻る必要があります-または)?

これは私が得る出力です-そしてプログラムはスタックしています!

アルフォンス:ガストンは私に頭を下げた!

ガストン:アルフォンスは私に頭を下げた!

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

synchronizedブロック/メソッドはthisに同期されます。つまり、ブロック/メソッドが呼び出されるオブジェクトインスタンスです。 (staticの場合、「オブジェクトインスタンス」は「クラスインスタンス」に置き換えられます。)

つまり、2つのオブジェクトが共通のオブジェクトではなく、それら自体に同期されます。

次のようなものを試してください:

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 (getClass()) {
            System.out.format("%s: %s  has bowed to me!%n", this.name, bower.getName());
            bower.bowBack(this);
         }
      }
      public void bowBack(Friend bower) {
         synchronized (getClass()) {
            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();
   }
}
23
Usagi Miyamoto

スレッド1:alphonseインスタンスはalphonse.bow(gaston);からロックされ、行を出力してgaston.bowBack()を呼び出します(ただし、gastonは同期のためにスレッド2からロックされますbow()インスタンスが呼び出されます)

スレッド2:gastonインスタンスはgaston.bow(alphonse);からロックされ、行を出力してalphonse.bowBack()を呼び出します(ただし、alphonseは同期のためにスレッド1からロックされていますbow()インスタンスが呼び出された)

したがって、どちらもリリースを待機しており、bow()メソッドを終了できないため、デッドロック

9
isah

まず、synchronizedusageが間違っています。 Oracle チュートリアル はうまく述べています:

まず、sameオブジェクトでの同期されたメソッドの2つの呼び出しがインターリーブすることは不可能です。

他の回答で説明したように、例に示されているコードdoesは「共通ロック」を使用しません(2つの異なるオブジェクトで同期されたメソッドは、「他の」メソッドの呼び出しに影響しません)。

それを超えて:すぐにremoveそれらのSystem.out.format()呼び出し-プログラムは(ほとんどの場合)notデッドロックに陥ります。

または:メインにprintln()を入れますbeforeスレッドを開始します-もう一度、プログラムはnotデッドロックします。

つまり、コンソールへの印刷には非常に時間がかかります。したがって、これはスレッドのtimingに劇的に影響します!ここで発生するのは、ほとんどの時間がこれらのコンソール出力アクションに費やされているということです。同じ名前を使用する同様の質問については ここ を参照してください;-)

あなたの例で何が起こるか:

  1. スレッドAlphonseは、関数bowを入力して、Alphonseへのロックを取得しています。

  2. スレッドガストンは、関数bowを入力してガストンへのロックを取得しています。

  3. スレッドアルフォンスは関数bowBackに入るためにガストンへのロックを要求していますが、そのロックは現在スレッドガストンによって保持されているため、アルフォンスは待機を強制されます。

  4. スレッドガストンは関数bowBackに入るためにアルフォンスへのロックを要求していますが、そのロックは現在スレッドアルフォンスによって保持されているため、ガストンは強制的に待機します。

デッドロック。

なぜこれが起こっているのか:

synchronized関数はsynchronized(this) { ... }の構文糖です。したがって、上記のクラスは次のように書くこともできます:

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

ただし、この例のthisはクラスのインスタンスであるため、各インスタンスには個別のロックがあります。クラスのすべてのインスタンスで同じオブジェクトをロックする場合は、次のように静的オブジェクトをロックする必要があります。

protected static final Object STATIC_LOCK = new Object();

public void bow(Friend bower) {
    synchronized (STATIC_LOCK) {
        System.out.format("%s: %s has bowed to me!%n", this.name, bower.getName());
        bower.bowBack(this);
    }
}

public void bowBack(Friend bower) {
    synchronized (STATIC_LOCK) {
        System.out.format("%s: %s has bowed back to me!%n", this.name, bower.getName());
    }
}

このLOCKオブジェクトは静的であるため、両方のスレッドがsameオブジェクトをロックし、お互いを正しくロックアウトします。この場合に強く推奨されるキーワードfinalに注意してください。そうしないと、実行中の同期ロックが(バグまたはコードの見落としによって)実行中に変更され、デッドロック状態に戻る可能性があるためです。上記とまったく同じ理由。

5
TwoThe