どのような状況で、空の同期ブロックが正しいスレッド セマンティクスを実現できますか?

StackOverflow https://stackoverflow.com/questions/686415

質問

私は何かを覗いていました バグを見つける 私のコードベースに関するレポートを作成したところ、トリガーされたパターンの 1 つは空のものでした。 synchronzied ブロック(すなわち、 synchronized (var) {})。の ドキュメントによると:

空の同期ブロックは、ほとんどの人が認識しているよりもはるかに微妙で正しく使用が困難であり、空の同期ブロックは、それほど考案されていないソリューションよりもほとんど良い解決策ではありません。

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

役に立ちましたか?

解決

誰もがそのシンクロナイザーを使用しなくなるまで、空の同期ブロックをお待ちしております。それはあなたが望むかもしれないが、あなたは同期ブロック内の後続のコードが保護されていないため、何も今までそれはあなたが、その後のコードを実行しながら、あなたが待っていた何であったかの変更から誰かを停止されません。それはあなたが望むものはほとんど決してません。

他のヒント

以前の回答では、空に関する最も有用な点を強調できていません。 synchronized ブロック:スレッド全体で変数の変更やその他のアクションを確実に可視化できます。として ユタールボルン 同期によりコンパイラに「メモリ バリア」が課せられ、キャッシュのフラッシュとリフレッシュが強制されることがわかります。しかし、「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、またはその両方でローカルにキャッシュされる可能性があります。したがって、B は A が書き込む 9 の値を決して読み取ることができないため、永久にループする可能性があります。

empty を使用して、スレッド間で変数の変更を可視にします。 synchronized ブロック

考えられる修正の 1 つは、 volatile (事実上「キャッシュなし」) 変数への修飾子。ただし、変数のキャッシュが完全に禁止されるため、これは非効率となる場合があります。空の synchronized 一方、ブロックはキャッシュを禁止しません。彼らが行うことは、特定の重要なポイントでキャッシュをメインメモリと強制的に同期させることだけです。例えば:*

int variable;

void test() // Corrected version
{
    new Thread( () ->  // A
    {
        variable = 9;
        synchronized( o ) {} // Flush to main memory
        for( ;; )
        {
            // Do other stuff
        }
    }).start();

    new Thread( () ->  // B
    {
        for( ;; )
        {
            synchronized( o ) {} // Refresh from main memory
            if( variable == 9 ) System.exit( 0 );
        }
    }).start();
}

final Object o = new Object();

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

可視性を保証するには、両方のスレッドが同じオブジェクト上で同期する必要があります。この保証は、 Javaメモリモデル, 特に、「モニター m のロック解除アクション」というルールに関して と同期します m" に対する後続のすべてのロック アクションと、それによる 前に起こる それらの行動。そこで、A は O のモニターの最後尾でロックを解除しました。 synchronized ブロックは、ブロックの先頭で B の後続のロックが発生する前に発生します。(注意してください、リレーションのこの奇妙な末尾の順序が、ボディが空になる理由を説明しています。) また、A の書き込みがロック解除に先立ち、B のロックが読み取りに先行することを考慮すると、リレーションは書き込みと読み取りの両方をカバーするように拡張する必要があります。 書き込みは読み取りの前に行われます. 。この重要な拡張された関係によって、修正されたプログラムがメモリ モデルの観点から正しくなります。

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


   * 私は、あたかもそれがプロセッサのキャッシュの問題であるかのように話します それは、それが見やすい見方だと思うからです。実際、Aleksandr Dubinsky 氏がコメントしたように、「最新のプロセッサはすべてキャッシュ コヒーレントです。」前発生関係は、CPU よりもコンパイラーが何を実行できるかに関係します。

これは、仕様が暗黙特定のメモリバリア操作が発生した場合に使用されます。ただし、仕様は現在、変更されており、オリジナルの仕様が正しく実装されていませんでした。別のスレッドがロックを解放するのを待つために使用することができるが、他のスレッドが既にロックを取得していることを調整することは難しいでしょう。

同期はただ待つだけではありませんが、洗練されていないコーディングでも必要な効果を達成できる可能性があります。

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

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

深さでのJavaのメモリモデルに見えるために、一連のGoogleの「プログラミング言語での高度なトピック」から、このビデオを見ています: http://www.youtube.com/watch?v=1FX4zco0ziYする

これは、コンパイラは(多くの場合、理論的には、時には実際に)あなたのコードに何ができるかの本当に素敵な概要を説明します。深刻なJavaプログラマのための不可欠なもの!

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top