43

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.

JustJeff
  • 12,640
  • 5
  • 49
  • 63
  • 1
    Makes one wonder why there is an AtomicInteger class (http://java.sun.com/javase/6/docs/api/java/util/concurrent/atomic/AtomicInteger.html), then. – Joey Jun 17 '09 at 12:26
  • 5
    @Johannes Rössel - there's more to AtomicInteger than not getting a corrupt value: visibility, CAS-based operations. – Robert Munteanu Jun 17 '09 at 12:30
  • 1
    Yup - I'd argue that in some ways AtomicInteger ought to be named VolatileInteger. – Jon Skeet Jun 17 '09 at 12:43
  • To clarify - I'm well aware that something like ++i has atomicity issues of its own; what I'm mainly concerned about is the observability of partially written values. @Jon Skeet, re "word tearing" - thank you for supplying the terminology =). And clearly, now we can worry that on a 64bit platform, accessing the int will incur word tearing as well.. – JustJeff Jun 17 '09 at 14:41

9 Answers9

65

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.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 1
    ...agreed with yours `it's not put very clear` in the JLS: your first quotation of 17.4.3 refers to sequential memory model, which they write is _not_ used in JVM (see small footnote there). – Ayrat Nov 09 '16 at 12:06
  • 1
    with the above being the case, what is the point of the AtomicInteger class? – Brad Ellis Jan 11 '18 at 06:09
  • 4
    @BradEllis: Two things: firstly, being atomic doesn't mean a change to a value is immediately visible, but `AtomicInteger` provides that. Secondly, `AtomicInteger` provides options to *modify* the value atomically - whereas something like `x += 2;` is *not* an atomic operation. If `x` starts with a value of 0, one thread performs `x += 1;` and another thread performs `x +=2;`, the final result for `x` could be 1, 2 or 3. With `AtomicInteger`, the equivalent operations will *always* result in 3. – Jon Skeet Jan 11 '18 at 12:41
29
  • 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.

reevesy
  • 3,452
  • 1
  • 26
  • 23
Robert Munteanu
  • 67,031
  • 36
  • 206
  • 278
  • 14
    +1 for "No amount of testing can prove thread safety - it can only disprove it;" – dfa Jun 17 '09 at 12:34
  • 6
    @dfa - that's what i meant by "absence of evidence that they are not does not imply that they are" – JustJeff Jun 17 '09 at 15:17
4

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.

James
  • 2,050
  • 13
  • 15
4

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.

Michal B
  • 41
  • 1
4

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?

Kounavi
  • 1,090
  • 1
  • 12
  • 24
  • What you're trying to explain is fine, but you can't synchronize on a primitive value. – Sotirios Delimanolis Jul 06 '14 at 04:38
  • @SotiriosDelimanolis Thanks for the heads up! I believe it's ok now! :) – Kounavi Jul 08 '14 at 10:52
  • 1
    In your last example, the field is static but the synchronized method isn't, which means that the synchronization will happen per-instance, which means that if different threads are using different object instances, then the access won't be synchronized between the threads (see https://stackoverflow.com/a/6214251/699700) – Max Nanasy Oct 19 '17 at 17:37
  • 1
    @MaxNanasy Indeed! You are correct :) It's been a long time since I've used JSR166y. Fixed! ;) P.S.: At that time, I was mostly concerned for single object instances with k threads and not n objects instances where each one has k threads and all of these can mix and match together! Apparently, multi-threading in Java can get (wildly) tricky making things elusive... :P – Kounavi Oct 19 '17 at 17:59
1

This is not atomic:

i++;

However, this is:

i = 5;

I think this is where some confusion sets in.

adekock11
  • 544
  • 2
  • 11
0

This is somewhat complicated, and is related to system wordsize. Bruce Eckel discusses it in more detail: Java Threads.

GaryF
  • 23,950
  • 10
  • 60
  • 73
0

Atomic reads and writes merely mean that you will never read, e.g. the first 16 bits of an int update and another from an old value.

This says nothing about WHEN other threads see these writes.

The long story short is that when two threads race with no memory barriers between them something gets lost.

Spin up two or more threads that increment a single shared integer and also count their own increments. When the integer gets to some value (INT_MAX, for example. Nice and large to let things warm up) stop everything and return the value of the int and the number of increments each thread performed.

    import java.util.Stack;

public class Test{

  static int ctr = Integer.MIN_VALUE;
  final static int THREADS = 4;

  private static void runone(){
    ctr = 0;
    Stack<Thread> threads = new Stack<>();
    for(int i = 0; i < THREADS; i++){
      Thread t = new Thread(new Runnable(){
        long cycles = 0;

        @Override
        public void run(){
          while(ctr != Integer.MAX_VALUE){
            ctr++;
            cycles++;
          }
          System.out.println("Cycles: " + cycles + ", ctr: " + ctr);
        }
      });
      t.start();
      threads.push(t);
    }
    while(!threads.isEmpty())
      try{
        threads.pop().join();
      }catch(InterruptedException e){
        // TODO Auto-generated catch block
        e.printStackTrace();
      }
    System.out.println();
  }

  public static void main(String args[]){
    System.out.println("Int Range: " + ((long) Integer.MAX_VALUE - (long) Integer.MIN_VALUE));
    System.out.println("  Int Max: " + Integer.MAX_VALUE);
    System.out.println();
    for(;;)
      runone();
  }
}

Here is the result of this test on my quad core box (feel free to play with the thread count in the code, I just matched my core count, obviously):

Int Range: 4294967295
Int Max: 2147483647

Cycles: 2145700893, ctr: 76261202
Cycles: 2147479716, ctr: 1825148133
Cycles: 2146138184, ctr: 1078605849
Cycles: 2147282173, ctr: 2147483647

Cycles: 2147421893, ctr: 127333260
Cycles: 2146759053, ctr: 220350845
Cycles: 2146742845, ctr: 450438551
Cycles: 2146537691, ctr: 2147483647

Cycles: 2110149932, ctr: 696604594
Cycles: 2146769437, ctr: 2147483647
Cycles: 2147095646, ctr: 2147483647
Cycles: 2147483647, ctr: 2147483647

Cycles: 2147483647, ctr: 330141890
Cycles: 2145029662, ctr: 2147483647
Cycles: 2143136845, ctr: 2147483647
Cycles: 2147007903, ctr: 2147483647

Cycles: 2147483647, ctr: 197621458
Cycles: 2076982910, ctr: 2147483647
Cycles: 2125642094, ctr: 2147483647
Cycles: 2125321197, ctr: 2147483647

Cycles: 2132759837, ctr: 330963474
Cycles: 2102475117, ctr: 2147483647
Cycles: 2147390638, ctr: 2147483647
Cycles: 2147483647, ctr: 2147483647
electroCutie
  • 194
  • 1
  • 8
0

When data is being shared between threads, synchronization is needed. When dealing with an Integer, which can go from main Memory to a processor's cache in a multiple processor system, a thread may be updating a local copy of an integer tied to a specific processor.

The volatile (See Wiki in Java Section) keyword in Java will ensure any update to an Integer will happen in memory and not a local copy.

Further, to synchronize updates to an Integer, consider using AtomicInteger. This implementation has a method (compareAndSet) to check if a value is what a thread expects AND set it if does. If it doesn't match, then another thread could have updated the value. The AtomicInteger will perform both the reading and updating of the Integer in an atomic operation, with the advantage of not having to block.

Rodrigo Gomez
  • 133
  • 1
  • 8