誰でも説明できます:
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();
}
}
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();
}
}
スレッド1:alphonse
インスタンスはalphonse.bow(gaston);
からロックされ、行を出力してgaston.bowBack()
を呼び出します(ただし、gaston
は同期のためにスレッド2からロックされますbow()
インスタンスが呼び出されます)
スレッド2:gaston
インスタンスはgaston.bow(alphonse);
からロックされ、行を出力してalphonse.bowBack()
を呼び出します(ただし、alphonse
は同期のためにスレッド1からロックされていますbow()
インスタンスが呼び出された)
したがって、どちらもリリースを待機しており、bow()
メソッドを終了できないため、デッドロック
まず、synchronizedのusageが間違っています。 Oracle チュートリアル はうまく述べています:
まず、sameオブジェクトでの同期されたメソッドの2つの呼び出しがインターリーブすることは不可能です。
他の回答で説明したように、例に示されているコードdoesは「共通ロック」を使用しません(2つの異なるオブジェクトで同期されたメソッドは、「他の」メソッドの呼び出しに影響しません)。
それを超えて:すぐにremoveそれらのSystem.out.format()
呼び出し-プログラムは(ほとんどの場合)notデッドロックに陥ります。
または:メインにprintln()
を入れますbeforeスレッドを開始します-もう一度、プログラムはnotデッドロックします。
つまり、コンソールへの印刷には非常に時間がかかります。したがって、これはスレッドのtimingに劇的に影響します!ここで発生するのは、ほとんどの時間がこれらのコンソール出力アクションに費やされているということです。同じ名前を使用する同様の質問については ここ を参照してください;-)
あなたの例で何が起こるか:
スレッドAlphonseは、関数bow
を入力して、Alphonseへのロックを取得しています。
スレッドガストンは、関数bow
を入力してガストンへのロックを取得しています。
スレッドアルフォンスは関数bowBack
に入るためにガストンへのロックを要求していますが、そのロックは現在スレッドガストンによって保持されているため、アルフォンスは待機を強制されます。
スレッドガストンは関数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
に注意してください。そうしないと、実行中の同期ロックが(バグまたはコードの見落としによって)実行中に変更され、デッドロック状態に戻る可能性があるためです。上記とまったく同じ理由。