27

コード ベースのFindbugsレポートを調べていたところ、トリガーされたパターンの 1 つが空のsynchronziedブロック (つまりsynchronized (var) {}) でした。ドキュメントには次のように書かれています:

空の同期ブロックは、ほとんどの人が認識しているよりもはるかに微妙であり、正しく使用するのが難しく、空の同期ブロックは、あまり工夫されていないソリューションよりも優れたソリューションになることはほとんどありません。

私の場合、ブロックの内容がコメントアウトされていたために発生しましたが、synchronizedステートメントはまだそこにありました。synchronized空のブロックが正しいスレッド セマンティクスを実現できるのはどのような状況ですか?

4

5 に答える 5

17

synchronized以前の回答は、空のブロックの最も有用な点、つまり変数の変更やその他のアクションをスレッド間で公開することを強調していないと思います。jtahlbornが示すように、同期は、コンパイラにメモリ バリアを課すことによってこれを行います。ただし、SnakE がこれについて議論したはずの場所が見つからなかったので、ここで私の言いたいことを説明します。

int variable;

void test() // This code is INCORRECT
{
    new Thread( () ->  // A
    {
        variable = 9;
        for( ;; )
        {
            // Do other stuff
        }
    }).start();

    new Thread( () ->  // B
    {
        for( ;; )
        {
            if( variable == 9 ) System.exit( 0 );
        }
    }).start();
}

上記のコードは正しくありません。コンパイラは、スレッド A の変数への変更を分離して、事実上それを B から隠し、永久にループする可能性があります。

空のsynchronizedブロックを使用してスレッド間で変更を公開する

volatile修正の 1 つは、変数に修飾子を追加することです。しかし、これは非効率的です。これにより、コンパイラはすべての変更を公開するように強制されます。これには、重要でない中間値が含まれる可能性があります。synchronized一方、空のブロックは、重要なポイントでのみ変更された値を公開します。例えば:

int variable;

void test() // Corrected version
{
    new Thread( () ->  // A
    {
        variable = 9;
        synchronized( o ) {} // Force exposure of the change
        for( ;; )
        {
            // Do other stuff
        }
    }).start();

    new Thread( () ->  // B
    {
        for( ;; )
        {
            synchronized( o ) {} // Look for exposed changes
            if( variable == 9 ) System.exit( 0 );
        }
    }).start();
}

final Object o = new Object();

メモリ モデルが可視性を保証する仕組み

可視性を保証するために、両方のスレッドが同じオブジェクトで同期する必要があります。保証はJava メモリ モデル、特に「モニター m でのロック解除アクションは、mでの後続のすべてのロック アクションと同期する」というルールに基づいており、したがって、これらのアクションの前に発生します。synchronizedしたがって、A のブロックの末尾にある o のモニターのロック解除は、B のブロックの先頭にある最終的なロックの前に発生します。また、A の書き込みはロック解除よりも先に行われ、B のロックは読み取りよりも先に行われるため、保証は書き込みと読み取りの両方をカバーするように拡張されます

synchronizedこれが空のブロックの最も重要な用途だと思います。

于 2015-08-11T04:31:25.483 に答える
4

同期は単に待機するだけではありませんが、洗練されていないコーディングでは必要な効果が得られる可能性があります。

http://www.javaperformancetuning.com/news/qotm030.shtmlから

  1. スレッドは、オブジェクト this のモニターのロックを取得します (モニターがロック解除されていると仮定します。それ以外の場合、スレッドはモニターがロック解除されるまで待機します)。
  2. スレッド メモリはすべての変数をフラッシュします。つまり、すべての変数が「メイン」メモリから効果的に読み込まれます (JVM はダーティ セットを使用してこれを最適化し、「ダーティ」変数のみがフラッシュされるようにしますが、概念的には同じです。セクションを参照してください)。 Java 言語仕様の 17.9)。
  3. コード ブロックが実行されます (この場合、戻り値を i3 の現在の値に設定します。これは、「メイン」メモリからリセットされたばかりの可能性があります)。
  4. (変数への変更は通常、「メイン」メモリに書き出されますが、geti3() では変更はありません。)
  5. スレッドは、オブジェクト this のモニターのロックを解放します。
于 2009-03-30T06:45:01.607 に答える