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
これが空のブロックの最も重要な用途だと思います。