Reputation: 51
achieve atomic operation because java volatile guarantees happens-before relation?
I have read about the happens-before for the volatile:
If Thread A writes to a volatile variable and Thread B subsequently reads the same volatilevariable, then all variables visible to Thread A before writing the volatile variable, will also be visible to Thread B after it has read the volatile variable.
Now, I have two varaibles:
static int m_x;
volatile static int m_y;
Now I have two threads, one only writes to them, and first write to m_x, and then write to m_y; The other one, only reads from them, first read m_y, and then, m_x.
My question is: is the write operation atomic? is the read operation atomic?
In my understanding, they should be atomic:
(1) On the side of writ thread, after (Write-1), it will not flush its cache to main-memory for m_x is NOT volatile, so, read-thread is not able to see the update; and after (Write-2), it will flush its cache to main-memory, for m_y is volatile;
(2) on the side of read thread, on (Read-1), it will update its cache from main memory, for m_y is volatile; and on (Read-2), it will NOT update its cache from main memory, for m_x is not volatile.
Because of the above two reason, I think the read thread should always observe the atomic value of the two variable. Right?
public class test {
static int m_x;
volatile static int m_y;
public static void main(String[] args) {
// write
new Thread() {
public
void run() {
while(true) {
int x = randInt(1, 1000000);
int y = -x;
m_x = x; // (Write-1)
m_y = y; // (Write-2)
}
}
}.start();
// read
new Thread() {
public
void run() {
while(true) {
int y = m_y; // (Read-1)
int x = m_x; // (Read-2)
int sum = y + x;
if (sum != 0) {
System.out.println("R:sum=" + sum);
System.out.println("R:x=" + x);
System.out.println("R:y=" + y);
System.out.println("\n");
}
}
}
}.start();
}
public static int randInt(int Min, int Max) {
return Min + (int)(Math.random() * ((Max - Min) + 1));
}
}
Upvotes: 2
Views: 142
Reputation: 719159
You assert that
"...after (Write-1), it will not flush its cache to main-memory for m_x is NOT volatile"
and
"... on (Read-2), it will NOT update its cache from main memory, for m_x is not volatile."
In fact, it is not possible to say whether or not the caches will be flushed (write), or whether or not values that are present in the cache (read). The JLS certainly does not make >>any<< guarantees for reads and writes to non-volatile variables. The guarantees apply to read and write operations on volatile variables ONLY.
While it is possible that you will observe consistent behavior for your program on certain platforms, that behavior is not guaranteed by the JLS.
Upvotes: 1
Reputation: 2338
As stated in the comment, the two reads and writes are not atomic. You cannot achieve atomicity by using the volatile
keyword.
This fact can be observed running your program.
To read/write both variables at the same time you either need proper synchronisation or create your own immutable value.
To do the later
public class ConcurrencyTestApp {
// use volatile for visibility
private volatile ImmutableValue immutableValue = new ImmutableValue(0, 0); // initial, non-null value
static class ImmutableValue {
private final int x;
private final int y;
ImmutableValue(final int x, final int y) {
this.x = x;
this.y = y;
}
int getX() {
return x;
}
int getY() {
return y;
}
@Override
public String toString() {
return String.format("x = %s\t| y = %s", x, y);
}
}
void replaceOldWithNewValue(final ImmutableValue newValue) {
immutableValue = newValue;
}
ImmutableValue getImmutableValue() {
return immutableValue;
}
static class Writer extends Thread {
private final ConcurrencyTestApp app;
Writer(ConcurrencyTestApp app) {
this.app = app;
}
volatile boolean isRunning = true;
@Override
public void run() {
while (isRunning) {
int x = randInt(1, 1000000);
int y = -x;
app.replaceOldWithNewValue(new ImmutableValue(x, y));
}
}
int randInt(int Min, int Max) {
return Min + (int) (Math.random() * ((Max - Min) + 1));
}
}
static class Reader extends Thread {
private final ConcurrencyTestApp app;
Reader(ConcurrencyTestApp app) {
this.app = app;
}
volatile boolean isRunning = true;
@Override
public void run() {
while (isRunning) {
ImmutableValue value = app.getImmutableValue();
System.out.println(value);
int x = value.getX();
int y = value.getY();
int sum = x + y;
if (sum != 0) {
System.out.println("R:sum=" + sum);
System.out.println("R:x=" + x);
System.out.println("R:y=" + y);
System.out.println("\n");
}
}
}
}
public static void main(String[] args) {
ConcurrencyTestApp app = new ConcurrencyTestApp();
Writer w = new Writer(app);
Reader r = new Reader(app);
w.start();
r.start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
w.isRunning = false;
r.isRunning = false;
}
}
For further reference, I recommend the book Java concurrency in practice by Brian Goetz and Tim Peierls.
Addendum
...
Because of the above two reason, I think the read thread should always observe the atomic value of the two variable. Right?
Wrong!
...and you are missing an important part.
For reference, see JSR 133 (Java Memory Model) FAQ by Jeremy Manson and Brian Goetz section What does volatile do?
In your program, there is nothing preventing the following:
Assume int m_x
= x1 and int m_y
= y1
Write-1
int m_x
is now set to value x2 (which may or may not be visible to your Reader-Thread)Read-1
and Read-2
(there is nothing stopping the Reader-Thread from doing that)
int y
= m_y
which is still y1 because your Writer-Thread did not execute further yetint x
= m_x
which may be x2 (but it also could still be x1)int m_y
is now set to value y2 (only now Read-1
would get y2 and Read-2
would be guaranteed to get x2 - unless your Writer-Thread continues)To see that yourself modify your writer
System.out.println("W0");
m_x = x; // non-volatile
System.out.println("W1: " + x);
m_y = y; // volatile
System.out.println("W2: " + y);
and reader thread code
System.out.println("R0");
int y = m_y; // volatile
System.out.println("R1: " + y);
int x = m_x; // non-volatile
System.out.println("R2: " + x);
So why does it not work for you?
From the reference
...volatile or not, anything that was visible to thread A when it writes to volatile field f becomes visible to thread B when it reads f.
Thus, your Reader thread is guaranteed to see the new values for m_x
and m_y
only when your Writer thread wrote the new value to m_y
. But because no particular thread execution order is guaranteed the write operation Write-2
may not happen before Read-1
is executed.
Also see Java Volatile Keyword by Jakob Jenkov for a similar example to yours.
Upvotes: 1