Home > Net >  Visibility of a read after synchronized block
Visibility of a read after synchronized block

Time:08-06

Does JMM guarantee the visibility of a synchronized write to the variable that is read in the other thread after a synchronized block? Here's what I mean:

public class SynchronizedWriteRead {

    private int a;
    private int b;

    public void writer() {
        synchronized (this) {
            a = 5;
            b = 7;
        }
    }

    public void reader() {
        synchronized (this) {
            int r1 = a; /* 5 */
        }
        int r2 = b; /* ? */
    }
}

JMM guarantees that an unlock on a monitor happens-before every subsequent lock on that monitor. But I'm not sure if it relates only to the synchronized block body or not.

Recently I'm encountered this post from Aleksey Shipilëv - Safe Publication and Safe Initialization in Java. It says:

Notice how doing synchronized in Unsafe DCL store does not help, contrary to layman belief it somehow magically "flushes the caches" or whatnot. Without a paired lock when reading the protected state, you are not guaranteed to see the writes preceding the lock-protected write.

So this is why I asked myself this question. I couldn't find an answer in the JLS.

Let's put it another way. Sometimes you're piggybacking on a volatile happens-before guarantee like this:

public class VolatileHappensBefore {

    private int a; /* specifically non-volatile */
    private volatile int b;

    public void writer() {
        a = 5;
        b = 7;
    }

    public void reader() {
        int r1 = b; /* 7 */
        int r2 = a; /* 5 */
    }
}

You're guaranteed to see both writes because sequential actions in the same thread are backed by happens-before, and happens-before itself is transitive.

Can I use a synchronized happens-before guarantee the same way? Maybe even like this (I've put sync variable to forbid the compiler/JVM to remove otherwise empty synchronized block):

    public void writer() {
        a = 5;
        b = 7;
        synchronized (this) {
            sync = 1;
        }
    }

    public void reader() {
        synchronized (this) {
            int r = sync;
        }
        int r1 = a; /* ? */
        int r2 = b; /* ? */
    }

CodePudding user response:

Does JMM guarantee the visibility of a synchronized write to the variable that is read in the other thread after a synchronized block?

Can I use a synchronized happens-before guarantee the same way?

Yes and yes.

synchronized and volatile give the same visibility guarantees.

In fact, a volatile variable behaves as if each read and write of this variable happens in its own tiny synchronized block.

In the JLS terms:

  • the visibility is guaranteed by the happens-before relation:

    Informally, a read r is allowed to see the result of a write w if there is no happens-before ordering to prevent that read.

  • volatile and synchronized are some of the ways to establish the happens-before relation:
    • An unlock on a monitor happens-before every subsequent lock on that monitor.
    • A write to a volatile field (§8.3.1.4) happens-before every subsequent read of that field.
    • ...

The quote from Safe Publication and Safe Initialization in Java describes the case when:

  • an object is initialized in a synchronized block in one thread
  • the object and is read without synchronized blocks in another thread.

In this situation the reader thread might see the object in the middle of initialization.

CodePudding user response:

What's important to note here is that happens-before is a transitive relation. So if A happens-before B and B happens-before C, we can safely conclude that A happens-before C.

Now let's look at the code in question (I've added comments for clarity):

public void writer() {
    a = 5; //1
    b = 7; //2
    synchronized (this) { 
        sync = 1;
    } //3
}

public void reader() {
    synchronized (this) { //4
        int r = sync;
    }
    int r1 = a; //5 
    int r2 = b; //6
}

We know that 1 happens-before 2 and 2 before 3 since they're executed by the same thread:

If x and y are actions of the same thread and x comes before y in program order, then hb(x, y).

We also know that 3 happens before 4:

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

If an action x synchronizes-with a following action y, then we also have hb(x, y).

Lastly, we know that 4 happens-before 5 and 5 before 6 since they're executed in the same thread.

If x and y are actions of the same thread and x comes before y in program order, then hb(x, y).

So we end up with a chain of happens-before relationships from 1 to 6. Consequently, 1 happens-before 6.

  • Related