In what situations could an empty synchronized block achieve correct threading semantics?

前端 未结 5 1233
离开以前
离开以前 2020-12-14 16:36

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

相关标签:
5条回答
  • 2020-12-14 17:07

    Synchronizing does a little bit more than just waiting, while inelegant coding this could achieve the effect required.

    From http://www.javaperformancetuning.com/news/qotm030.shtml

    1. The thread acquires the lock on the monitor for object this (assuming the monitor is unlocked, otherwise the thread waits until the monitor is unlocked).
    2. The thread memory flushes all its variables, i.e. it has all of its variables effectively read from "main" memory (JVMs can use dirty sets to optimize this so that only "dirty" variables are flushed, but conceptually this is the same. See section 17.9 of the Java language specification).
    3. The code block is executed (in this case setting the return value to the current value of i3, which may have just been reset from "main" memory).
    4. (Any changes to variables would normally now be written out to "main" memory, but for geti3() we have no changes.)
    5. The thread releases the lock on the monitor for object this.
    0 讨论(0)
  • 2020-12-14 17:13

    For an in depth look into Java's memory model, have a look at this video from Google's 'Advanced topics in programming languages' series: http://www.youtube.com/watch?v=1FX4zco0ziY

    It gives a really nice overview of what the compiler can (often in theory, but sometimes in practice) do to your code. Essential stuff for any serious Java programmer!

    0 讨论(0)
  • 2020-12-14 17:20

    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.

    Using empty synchronized blocks to expose a change across threads

    One 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();
    

    How the memory model guarantees visibility

    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.

    0 讨论(0)
  • 2020-12-14 17:24

    An empty synchronized block will wait until nobody else is using that synchronizer. That may be what you want, but because you haven't protected the subsequent code in the synchronized block, nothing is stopping somebody else from modifying what ever it was you were waiting for while you run the subsequent code. That's almost never what you want.

    0 讨论(0)
  • 2020-12-14 17:31

    It used to be the case that the specification implied certain memory barrier operations occurred. However, the spec has now changed and the original spec was never implemented correctly. It may be used to wait for another thread to release the lock, but coordinating that the other thread has already acquired the lock would be tricky.

    0 讨论(0)
提交回复
热议问题