We know that long and double assignments are not atomic in Java until they are declared volatile. My question is how does it really matter in our programming practice. for inst
I made a cool little example of this a while ago
public class UnatomicLong implements Runnable {
private static long test = 0;
private final long val;
public UnatomicLong(long val) {
this.val = val;
}
@Override
public void run() {
while (!Thread.interrupted()) {
test = val;
}
}
public static void main(String[] args) {
Thread t1 = new Thread(new UnatomicLong(-1));
Thread t2 = new Thread(new UnatomicLong(0));
System.out.println(Long.toBinaryString(-1));
System.out.println(pad(Long.toBinaryString(0), 64));
t1.start();
t2.start();
long val;
while ((val = test) == -1 || val == 0) {
}
System.out.println(pad(Long.toBinaryString(val), 64));
System.out.println(val);
t1.interrupt();
t2.interrupt();
}
// prepend 0s to the string to make it the target length
private static String pad(String s, int targetLength) {
int n = targetLength - s.length();
for (int x = 0; x < n; x++) {
s = "0" + s;
}
return s;
}
}
One thread constantly tries to assign 0
to test
while the other tries to assign -1
. Eventually you'll end up with a number that's either 0b1111111111111111111111111111111100000000000000000000000000000000
or
0b0000000000000000000000000000000011111111111111111111111111111111
.
(Assuming you aren't on a 64 bit JVM. Most, if not all, 64 bit JVMs will actually do atomic assignment for long
s and double
s.)
It makes a difference if SharedInt or SharedLong are going to be accessed simultaneously. As you said, one thread may read a stale int, or a stale or corrupted long.
This could be important if the value was being used to reference an array.
Or with display in a GUI.
How about writing some values over a network and sending bad data. Now clients are confused or crashing.
Incorrect values could be stored to a database.
Repeated calculations could be corrupted...
As you requested in comments, For long specifically:
Long values are frequently used for time calculations. This could throw off loops where you are waiting for an amount of time before performing some operation, such as a heartbeat in a networking app.
You could report to a client synchronizing clocks with you time was 80 years or 1000 years in the past.
Longs and ints are commonly used for bitpacked fields to indicate many different things. Your flags would be entirely corrupted.
Longs are used as unique ID's frequently. This could corrupt hash tables you're creating.
Obviously lots of bad, bad stuff could happen. If this value needs to be thread safe, and you want your software to be very reliable, declare these variables volatile, use an Atomic variable, or synchronize access and set methods.
Where improper programming with an int
may result in stale values being observed, improper programming with a long
may result in values that never actually existed being observed.
This could theoretically matter for a system that only needs to be eventually-correct and not point-in-time correct, so skipped synchronization for performance. Although skipping a volatile field declaration in the interest of performance seems on casual inspection like foolishness.