この行:
public synchronized void playTurn(){
//code
}
動作はと同等です
public void playTurn() {
synchronized(this) {
//code
}
}
Brian Agnewが指摘したように、スレッドは2つの異なるオブジェクト(thread1、thread2)で同期しており、それぞれが独自のインスタンスであるため、効果的な同期が行われないため、同期が発生しないのはそのためです。
同期にターン変数を使用する場合、例:
private static String turn = ""; // must initialize or you ll get an NPE
public void playTurn() {
synchronized(turn) {
//...
turn = msg; // (1)
//...
}
}
その場合、状況ははるかに良くなります(検証のために複数回実行します)が、100%の同期もありません。最初は(ほとんどの場合)ダブルpingとダブルポンが発生し、その後は同期しているように見えますが、それでもダブルping/ポンを取得できます。
同期されたブロックは、その値への参照ではなく、値(このすばらしい回答を参照)をロックします。(編集を参照)
それでは、考えられる1つのシナリオを見てみましょう。
thread1 locks on ""
thread2 blocks on ""
thread1 changes the value of turn variable to "PING" - thread2 can continue since "" is no longer locked
入れてみたことを確認する
try {
Thread.currentThread().sleep(1000); // try with 10, 100 also multiple times
}
catch (InterruptedException ex) {}
前後
turn = msg;
そしてそれは同期しているように見えますか?!しかし、あなたが置くなら
try {
Thread.yield();
Thread.currentThread().sleep(1000); // also try multiple times
}
catch (InterruptedException ex) {}
数秒後、二重のping/pongsが表示されます。Thread.yield()は、本質的に「プロセッサを使い終わったので、他のスレッドを動作させる」ことを意味します。これは明らかに私のOSでのシステムスレッドスケジューラの実装です。
したがって、正しく同期するには、行を削除する必要があります
turn = msg;
スレッドが常に同じ値で同期できるように-実際にはそうではありません:)上記の素晴らしい答えで説明されているように-文字列(不変オブジェクト)はロックとして危険です-プログラムの100か所に文字列「A」を作成すると100か所すべて参照(変数)はメモリ内の同じ「A」を指すため、をオーバーシンクロする可能性があります。
したがって、元の質問に答えるには、次のようにコードを変更します。
public void playTurn() {
synchronized(PingPongThread.class) {
//code
}
}
並列のPingPongの例は、100%正しく実装されます(EDIT ^ 2を参照)。
上記のコードは次と同等です。
public static synchronized void playTurn() {
//code
}
PingPongThread.classはClassオブジェクトです。たとえば、すべてのインスタンスで、常に1つのインスタンスしかないgetClass()を呼び出すことができます。
また、あなたはこのようにすることができます
public static Object lock = new Object();
public void playTurn() {
synchronized(lock) {
//code
}
}
また、このチュートリアルの例(必要に応じて複数回実行)を読んでプログラムしてください。
編集:
技術的に正しいこと:
同期メソッドは、これをロックする同期ステートメントと同じです。同期ステートメントの引数を「lock」と呼びましょう。Markoが指摘したように、「lock」はクラスのオブジェクト/インスタンスへの参照を格納する変数です。仕様を引用するには:
同期ステートメントは、オブジェクトへの参照を計算します。次に、そのオブジェクトのモニターでロックアクションを実行しようとします。
したがって、同期は実際には値(オブジェクト/クラスインスタンス)ではなく、そのインスタンス/値に関連付けられたオブジェクトモニターで行われます。なぜなら
Javaの各オブジェクトは、モニターに関連付けられています。
効果は同じままです。
編集^2:
コメントのフォローアップコメント:「並列PingPongの例は100%正しく実装されます」-つまり、目的の動作が(エラーなしで)達成されます。
私見、結果が正しければ解決策は正しいです。問題を解決する方法はたくさんあるので、次の基準はソリューションの単純さ/優雅さです-Markoが言い換えると、いくつかのコメントでフェイザーを使用してエラーが発生する可能性がはるかに少ないため、フェイザーソリューションがより良いアプローチです同期メカニズムを使用するよりもオブジェクト-この投稿のすべての(非)ソリューションバリアントから見ることができます。注目すべきは、コードサイズと全体的な明快さの比較です。
結論として、この種の構成は、問題の問題に適用できる場合は常に使用する必要があります。