I was looking through a Findbugs report on my code base and one of the patterns that was triggered was for an empty synchronzied block (i.e. synchronized
I think the earlier answers fail to underline the most useful thing about empty synchronized blocks: exposing variable changes and other actions across threads. As jtahlborn indicates, synchronization does this by imposing a memory barrier on the compiler. I didn’t find where SnakE is supposed to have discussed this, though, so here I explain what I mean.
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();
}
The code above is incorrect. The compiler might isolate thread A’s change to the variable, effectively hiding it from B, which would then loop forever.
synchronized blocks to expose a change across threadsOne correction is to add a volatile modifier to the variable. But this can be inefficient; it forces the compiler to expose all changes, which might include intermediate values of no interest. Empty synchronized blocks, on the other hand, only expose the changed value at critical points. For example:
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();
Both threads must synchronize on the same object to guarantee visibility. The guarantee rests on the Java memory model, in particular on the rule that an “unlock action on monitor m synchronizes-with all subsequent lock actions on m” and thereby happens-before those actions. So the unlock of o’s monitor at the tail of A’s synchronized block happens-before the eventual lock at the head of B’s block. And because A’s write precedes its unlock and B’s lock precedes its read, the guarantee extends to cover both write and read — write happens-before read — making the revised program correct in terms of the memory model.
I think this is the most important use for empty synchronized blocks.