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 asynchronized
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
andsynchronized
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
.