Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 941
yankee Avatar asked May 10 '16 15:05

yankee


People also ask

What happens when synchronized object is invoked?

When a thread invokes a synchronized method, it automatically acquires the intrinsic lock for that method's object and releases it when the method returns. The lock release occurs even if the return was caused by an uncaught exception.

Why must wait () always be in synchronized block?

As Michael Borgwardt points out, wait/notify is all about communication between threads, so you'll always end up with a race condition similar to the one described above. This is why the "only wait inside synchronized" rule is enforced.

Does synchronized method lock whole object?

When we use a synchronized block, Java internally uses a monitor, also known as a monitor lock or intrinsic lock, to provide synchronization. These monitors are bound to an object; therefore, all synchronized blocks of the same object can have only one thread executing them at the same time.

Why are locks better than synchronized?

Only one thread is allowed to access only one method at any given point of time using a synchronized block. This is a very expensive operation. Locks avoid this by allowing the configuration of various locks for different purpose.


2 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.

like image 110
yshavit Avatar answered Sep 22 '22 20:09

yshavit


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.

like image 43
Solomon Slow Avatar answered Sep 24 '22 20:09

Solomon Slow