Home » Java » Why is `synchronized (new Object()) {}` a no-op?

Why is `synchronized (new Object()) {}` a no-op?

Posted by: admin December 20, 2017 Leave a comment

Questions:

In the following code:

class A {
    private int number;

    public void a() {
        number = 5;
    }

    public void b() {
        while(number == 0) {
            // ...
        }
    }
}

If method b is called and then a new thread is started which fires method a, then method b is not guaranteed to ever see the change of number and thus b may never terminate.

Of course we could make number volatile to resolve this. However for academic reasons let’s assume that volatile is not an option:

The JSR-133 FAQs tells us:

After we exit a synchronized block, we release the monitor, which has the effect of flushing the cache to main memory, so that writes made by this thread can be visible to other threads. Before we can enter a synchronized block, we acquire the monitor, which has the effect of invalidating the local processor cache so that variables will be reloaded from main memory.

This sounds like I just need both a and b to enter and exit any synchronized-Block at all, no matter what monitor they use. More precisely it sounds like this…:

class A {
    private int number;

    public void a() {
        number = 5;
        synchronized(new Object()) {}
    }

    public void b() {
        while(number == 0) {
            // ...
            synchronized(new Object()) {}
        }
    }
}

…would eliminate the problem and will guarantee that b will see the change to a and thus will also eventually terminate.

However the FAQs also clearly state:

Another implication is that the following pattern, which some people
use to force a memory barrier, doesn’t work:

synchronized (new Object()) {}

This is actually a no-op, and your compiler can remove it entirely,
because the compiler knows that no other thread will synchronize on
the same monitor. You have to set up a happens-before relationship for
one thread to see the results of another.

Now that is confusing. I thought that the synchronized-Statement will cause caches to flush. It surely can’t flush a cache to main memory in way that the changes in the main memory can only be seen by threads which synchronize on the same monitor, especially since for volatile which basically does the same thing we don’t even need a monitor, or am I mistaken there? So why is this a no-op and does not cause b to terminate by guarantee?

Answers:

The FAQ is not the authority on the matter; the JLS is. Section 17.4.4 specifies synchronizes-with relationships, which feed into happens-before relationships (17.4.5). The relevant bullet point is:

  • An unlock action on monitor m synchronizes-with all subsequent lock actions on m (where “subsequent” is defined according to the synchronization order).

Since m here is the reference to the new Object(), and it’s never stored or published to any other thread, we can be sure that no other thread will acquire a lock on m after the lock in this block is released. Furthermore, since m is a new object, we can be sure that there is no action that previously unlocked on it. Therefore, we can be sure that no action formally synchronizes-with this action.

Technically, you don’t even need to do a full cache flush to be up to the JLS spec; it’s more than the JLS requires. A typical implementation does that, because it’s the easiest thing the hardware lets you do, but it’s going “above and beyond” so to speak. In cases where escape analysis tells an optimizing compiler that we need even less, the compiler can perform less. In your example, escape analysis can could tell the compiler that the action has no effect (due to the reasoning above) and can be optimized out entirely.

Questions:
Answers:

the following pattern, which some people use to force a memory barrier, doesn’t work:

It’s not guaranteed to be a no-op, but the spec permits it to be a no-op. The spec only requires synchronization to establish a happens-before relationship between two threads when the two threads synchronize on the same object, but it actually would be easier to implement a JVM where the identity of the object did not matter.

I thought that the synchronized-Statement will cause caches to flush

There is no “cache” in the Java Language Specification. That’s a concept that only exists in the details of some (well, O.K., virtually all) hardware platforms and JVM implementations.