Simeon Leyzerzon Simeon Leyzerzon - 2 months ago 10
Java Question

Do atomics in Java guarantee ordering or only uniqueness?

When executing the following snippet I'm seeing that results are getting properly and uniquely incremented however they are printed out of order (as shown below):

import java.util.concurrent.atomic.AtomicInteger;

public class Atomics {

private AtomicInteger index = new AtomicInteger(0);

public static void main(String [] args) {
new Atomics().updateIndexViaCAS();
}

private void updateIndexViaCAS() {

Runnable target = () -> {
for(int i = 0; i<10;i++) {
cas:
while (true) {
int oldValue = index.get();
int newValue = oldValue + 1;
if(index.compareAndSet(oldValue, newValue)) {
System.out.println(Thread.currentThread() + ": "+newValue); //order is not guaranteed?
break cas;
}
}
}
};

/*Runnable target = () -> {
for (int i = 0; i < 10; i++) {
int oldValue = index.get();
int newValue = oldValue + 1;
do {
oldValue = index.get();
} while (!index.compareAndSet(oldValue, newValue));
System.out.println(Thread.currentThread() + ": "+newValue);
}
};*/

for(int t=0; t<2;t++) {
Thread th = new Thread(target);
th.start();
}


}
}


Sample Results:

Thread[Thread-0,5,main]: 1
Thread[Thread-0,5,main]: 3
Thread[Thread-0,5,main]: 4
Thread[Thread-0,5,main]: 5
Thread[Thread-0,5,main]: 6
Thread[Thread-1,5,main]: 2 <-- out of order
Thread[Thread-0,5,main]: 7
Thread[Thread-0,5,main]: 9
Thread[Thread-0,5,main]: 10
Thread[Thread-0,5,main]: 11
Thread[Thread-0,5,main]: 12
Thread[Thread-1,5,main]: 8 <-- out of order
Thread[Thread-1,5,main]: 13
Thread[Thread-1,5,main]: 14
Thread[Thread-1,5,main]: 15
Thread[Thread-1,5,main]: 16
Thread[Thread-1,5,main]: 17
Thread[Thread-1,5,main]: 18
Thread[Thread-1,5,main]: 19
Thread[Thread-1,5,main]: 20


Is it because:


  1. There's something wrong with the code (and if so how to fix it to enforce ordering)?

  2. Is it the proper behavior of atomics because by design they do not guarantee the execution order but only guarantee lockless concurrency?

  3. Something else at work here?



TIA.

To address my confusion with the comments below - why would I need the Atomics in the first place if I still need to use synchronization to view what is going on like below?

Runnable target = () -> {
for (int i = 0; i < 10; i++) {
cas: while (true) {
synchronized (mutex) {
int oldValue = index.get();
int newValue = oldValue + 1;
if (index.compareAndSet(oldValue, newValue)) {
System.out.println(Thread.currentThread() + ": "
+ newValue); // order is not guaranteed?
break cas;
}
}
}
}
};

Answer

There is no guarantee how your code interleaves. In your case, I think the confusion is that the loop is not executed as a unit but can interleave in any way. Think of thread 1 stopping before executing System.out.println(Thread.currentThread() + ": " + newValue) where newValue was incremented from 0 to 1 so nothing is yet printed. Thread 2 can then increment from 1 to 2 and print its outpout before the other thread. This will cause the bigger number being printed first.

Also, remember that System.out.println synchronizes on System.out. This is why you always observe chunks of prints in the right order. One thread is probably finding System.out locked and supsends its activity for a short while before resuming its activity.

Comments