Why is i++
not atomic in Java?
To get a bit deeper in Java I tried to count how often the loop in threads are executed.
So I used a
private static int total = 0;
in the main class.
I have two threads.
System.out.println("Hello from Thread 1!");
System.out.println("Hello from Thread 2!");
And I count the lines printed by thread 1 and thread 2. But the lines of thread 1 + lines of thread 2 don't match the total number of lines printed out.
Here is my code:
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.logging.Level; import java.util.logging.Logger; public class Test { private static int total = 0; private static int countT1 = 0; private static int countT2 = 0; private boolean run = true; public Test() { ExecutorService newCachedThreadPool = Executors.newCachedThreadPool(); newCachedThreadPool.execute(t1); newCachedThreadPool.execute(t2); try { Thread.sleep(1000); } catch (InterruptedException ex) { Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex); } run = false; try { Thread.sleep(1000); } catch (InterruptedException ex) { Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex); } System.out.println((countT1 + countT2 + " == " + total)); } private Runnable t1 = new Runnable() { @Override public void run() { while (run) { total++; countT1++; System.out.println("Hello #" + countT1 + " from Thread 2! Total hello: " + total); } } }; private Runnable t2 = new Runnable() { @Override public void run() { while (run) { total++; countT2++; System.out.println("Hello #" + countT2 + " from Thread 2! Total hello: " + total); } } }; public static void main(String[] args) { new Test(); } }
a : not relating to, concerned with, or composed of atoms Gerald Cleaver, professor and graduate program director in Baylorʼs department of physics, will present "Life on the Landscape," which will consider the place of the Earthʼs universe and the possibility of nonatomic-based (intelligent) life forms outside of it. ...
It's not atomic because it's a multiple-step operation at the machine code level. That is, longs and doubles are longer than the processor's word length.
On objects without an atomic type, standard never defines ++ as an atomic operation.
The increment-memory machine instruction on an X86 is atomic only if you use it with a LOCK prefix. x++ in C and C++ doesn't have atomic behavior.
i++
involves two operations :
i
i
When two threads perform i++
on the same variable at the same time, they may both get the same current value of i
, and then increment and set it to i+1
, so you'll get a single incrementation instead of two.
Example :
int i = 5; Thread 1 : i++; // reads value 5 Thread 2 : i++; // reads value 5 Thread 1 : // increments i to 6 Thread 2 : // increments i to 6 // i == 6 instead of 7
i++
is probably not atomic in Java because atomicity is a special requirement which is not present in the majority of the uses of i++
. That requirement has a significant overhead: there is a large cost in making an increment operation atomic; it involves synchronization at both the software and hardware levels that need not be present in an ordinary increment.
You could make the argument that i++
should have been designed and documented as specifically performing an atomic increment, so that a non-atomic increment is performed using i = i + 1
. However, this would break the "cultural compatibility" between Java, and C and C++. As well, it would take away a convenient notation which programmers familiar with C-like languages take for granted, giving it a special meaning that applies only in limited circumstances.
Basic C or C++ code like for (i = 0; i < LIMIT; i++)
would translate into Java as for (i = 0; i < LIMIT; i = i + 1)
; because it would be inappropriate to use the atomic i++
. What's worse, programmers coming from C or other C-like languages to Java would use i++
anyway, resulting in unnecessary use of atomic instructions.
Even at the machine instruction set level, an increment type operation is usually not atomic for performance reasons. In x86, a special instruction "lock prefix" must be used to make the inc
instruction atomic: for the same reasons as above. If inc
were always atomic, it would never be used when a non-atomic inc is required; programmers and compilers would generate code that loads, adds 1 and stores, because it would be way faster.
In some instruction set architectures, there is no atomic inc
or perhaps no inc
at all; to do an atomic inc on MIPS, you have to write a software loop which uses the ll
and sc
: load-linked, and store-conditional. Load-linked reads the word, and store-conditional stores the new value if the word has not changed, or else it fails (which is detected and causes a re-try).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With