Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

long and double assignments are not atomic - How does it matter?

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 instance if you the see below classes whose objects are being shared among multiple threads.

/**
*  The below class is not thread safe. the assignments to int values would be 
*  atomic but at the same time it not guaranteed that changes would be visible to 
*  other threads.
**/
public final class SharedInt {

   private int value;

   public void setValue(int value) {
      this.value = value;
   }

   public int getValue() {
      return this.value;
   }

}

Now consider another SharedLong

/**
* The below class is not thread safe because here the assignments to  long 
*  are not atomic as well as changes are not
*  guaranteed to be visible to other threads.
*/
public final class SharedLong {

    private long value;

    public void setValue(long  value) {
      this.value = value;
    }

    public long getValue() {
       return this.values;
    }
}

Now we can see the both of the above versions are not thread safe. In case of int, it is because threads may see stale values of integer. While in case if long, they can see corrupt as well as stale values of long variable.

In both cases, if an instance is not shared among multiple threads, then the classes are safe.

To make the above classes thread safe we need to declare int and long both to be volatile or make the method synchronized. This make me wonder: How does it really matter if assignments to long and double are not atomic during our normal course of programming because both need to be declared volatile or synchronized for multithreaded access so my Questions is What are the scenarios where the fact that long assignments are not atomic may make a difference?.

like image 429
veritas Avatar asked Jul 05 '13 04:07

veritas


3 Answers

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 longs and doubles.)

like image 191
Jeffrey Avatar answered Oct 21 '22 23:10

Jeffrey


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.

like image 28
Affe Avatar answered Oct 21 '22 21:10

Affe


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.

like image 32
William Morrison Avatar answered Oct 21 '22 21:10

William Morrison