Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Are java primitive ints atomic by design or by accident?

Are java primitive integers (int) atomic at all, for that matter? Some experimentation with two threads sharing an int seems to indicate that they are, but of course absence of evidence that they are not does not imply that they are.

Specifically, the test I ran was this:

public class IntSafeChecker {
    static int thing;
    static boolean keepWatching = true;

    // Watcher just looks for monotonically increasing values   
    static class Watcher extends Thread {
        public void run() {
            boolean hasBefore = false;
            int thingBefore = 0;

            while( keepWatching ) {
                // observe the shared int
                int thingNow = thing;
                // fake the 1st value to keep test happy
                if( hasBefore == false ) {
                    thingBefore = thingNow;
                    hasBefore = true;
                }
                // check for decreases (due to partially written values)
                if( thingNow < thingBefore ) {
                    System.err.println("MAJOR TROUBLE!");
                }
                thingBefore = thingNow;
            }
        }
    }

    // Modifier just counts the shared int up to 1 billion
    static class Modifier extends Thread {
        public void run() {
            int what = 0;
            for(int i = 0; i < 1000000000; ++i) {
                what += 1;
                thing = what;
            }
            // kill the watcher when done
            keepWatching = false;
        }
    }

    public static void main(String[] args) {
        Modifier m = new Modifier();
        Watcher w = new Watcher();
        m.start();
        w.start();
    }
}

(and that was only tried with java jre 1.6.0_07 on a 32bit windows PC)

Essentially, the Modifier writes a count sequence to the shared integer, while the Watcher checks that the observed values never decrease. On a machine where a 32 bit value had to be accessed as four separate bytes (or even two 16bit words), there would be a probability that Watcher would catch the shared integer in an inconsistent, half-updated state, and detect the value decreasing rather than increasing. This should work whether the (hypothetical) data bytes are collected/written LSB 1st or MSB 1st, but is only probablistic at best.

It would seem very probable given today's wide data paths that a 32 bit value could be effectively atomic, even if the java spec doesn't require it. In fact, with a 32 bit data bus it would seem that you might have to work harder to get atomic access to bytes than to 32 bit ints.

Googling on "java primitive thread safety" turns up loads of stuff on thread-safe classes and objects, but looking for the info on the primitives seems to be looking for the proverbial needle in a haystack.

like image 288
JustJeff Avatar asked Jun 17 '09 12:06

JustJeff


6 Answers

All memory accesses in Java are atomic by default, with the exception of long and double (which may be atomic, but don't have to be). It's not put very clearly to be honest, but I believe that's the implication.

From section 17.4.3 of the JLS:

Within a sequentially consistent execution, there is a total order over all individual actions (such as reads and writes) which is consistent with the order of the program, and each individual action is atomic and is immediately visible to every thread.

and then in 17.7:

Some implementations may find it convenient to divide a single write action on a 64-bit long or double value into two write actions on adjacent 32 bit values. For efficiency's sake, this behavior is implementation specific; Java virtual machines are free to perform writes to long and double values atomically or in two parts.

Note that atomicity is very different to volatility though.

When one thread updates an integer to 5, it's guaranteed that another thread won't see 1 or 4 or any other in-between state, but without any explicit volatility or locking, the other thread could see 0 forever.

With regard to working hard to get atomic access to bytes, you're right: the VM may well have to try hard... but it does have to. From section 17.6 of the spec:

Some processors do not provide the ability to write to a single byte. It would be illegal to implement byte array updates on such a processor by simply reading an entire word, updating the appropriate byte, and then writing the entire word back to memory. This problem is sometimes known as word tearing, and on processors that cannot easily update a single byte in isolation some other approach will be required.

In other words, it's up to the JVM to get it right.

like image 151
Jon Skeet Avatar answered Oct 03 '22 15:10

Jon Skeet


  • No amount of testing can prove thread safety - it can only disprove it;
  • I found a indirect reference in JLS 17.7 which states

Some implementations may find it convenient to divide a single write action on a 64-bit long or double value into two write actions on adjacent 32 bit values.

and further down

For the purposes of the Java programming language memory model, a single write to a non-volatile long or double value is treated as two separate writes: one to each 32-bit half.

This seems to imply that writes to ints are atomic.

like image 29
Robert Munteanu Avatar answered Oct 03 '22 15:10

Robert Munteanu


A read or write from integer or any smaller type should be atomic, but as Robert noted, longs and doubles may or may not depending on the implementation. However, any operation that uses both a read and a write, including all of the increment operators, are not atomic. Thus, if you have to threads operating on an integer i=0, one does i++ and the other does i=10, the result could be 1, 10, or 11.

For operations like this, you should look at AtomicInteger which has methods for atomically modifying a value while retrieving the old one or to atomically increment the value.

Finally, threads may cache the value of the variable and won't see changes made to it from other threads. To make sure that both threads always see changes made by the other thread, you need to mark the variable as being volatile.

like image 22
James Avatar answered Oct 03 '22 16:10

James


I think it doesnt work as you expected:

private static int i = 0;
public void increment() {
   synchronized (i) { 
      i++; 
   }
}

integer is immutable so you are sychronizing on a different object all the time. int "i" is autoboxed to Integer object, then you set lock on it. If another thread goes into this method int i is autoboxed to another Integer object and you set lock on a different object then before.

like image 40
Michal B Avatar answered Oct 03 '22 15:10

Michal B


I agree with Jon Skeet and I would like to add that many people confuse the concept of atomicity, volatility and thread safety because sometimes the terms are used interchangeably.
For example, consider this:

private static int i = 0;
public void increment() { i++; }

While someone may argue that this operation is atomic, the referred hypothesis is wrong.
The statement i++; performs three operations:
1) Read
2) Update
3) Write

Therefore, the threads that operate on this variable should be synchronized like this:

private static int i = 0;
private static final Object LOCK = new Object();
public void increment() {
   synchronized(LOCK) {
       i++;
    } 
}

or this:

private static int i = 0;
public static synchronized void increment() {
   i++; 
}

Do note that, for a single object instance, calling a method that is being accessed by multiple threads and operate on shared mutable data one has to take into consideration the fact that a method's parameters, local variable and return value are local for each Thread.

For more information check out this link:
http://www.javamex.com/tutorials/synchronization_volatile.shtml

Hope this helps.

UPDATE: There is also the case where you can synchronize on the class object itself. More info here: How to synchronize a static variable among threads running different instances of a class in java?

like image 37
Kounavi Avatar answered Oct 03 '22 16:10

Kounavi


This is not atomic:

i++;

However, this is:

i = 5;

I think this is where some confusion sets in.

like image 37
adekock11 Avatar answered Oct 03 '22 16:10

adekock11