When I run the following code, the final output is always positive, and if I switch the order of the "x++" and the "x--" then the final output is always negative. Is there some semblance of order to which parts of this race condition get skipped? Any help understanding is greatly appreciated!
public class DataRace {
private static class MyThreadCode implements Runnable {
private static int x = 0; // NOTE THAT X IS STATIC!!!
@Override
public void run() {
for (int i = 0; i < 10000000; i++) {
x++;
x--;
}
System.out.println(x + " " + Thread.currentThread().getName());
}
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
Thread t = new Thread(new MyThreadCode());
t.start();
}
}
}
A race condition is an undesirable situation that occurs when a device or system attempts to perform two or more operations at the same time, but because of the nature of the device or system, the operations must be done in the proper sequence to be done correctly.
When race conditions occur. A race condition occurs when two threads access a shared variable at the same time. The first thread reads the variable, and the second thread reads the same value from the variable.
It can be eliminated by using no more than two levels of gating. An essential race condition occurs when an input has two transitions in less than the total feedback propagation time. Sometimes they are cured using inductive delay line elements to effectively increase the time duration of an input signal.
A race condition occurs when the timing or order of events affects the correctness of a piece of code. A data race occurs when one thread accesses a mutable object while another thread is writing to it.
We have two problems here:
x++
and x--
are not atomic (see Why is i++ not atomic?), you get a race condition.
Thread 1 will load value of x
(0). The CPU could then switch to Thread 2, which also loads the current x
(0). Now both increment the value locally and later, they will set x
. This means we lose an increment.x
is not marked volatile
nor are we using the synchronized
keyword, you have no guarantee that a thread really sees the actual value of x
. It could be, that the value has already been updated by another thread, but because of the Java visibility guarantees, you have no guarantee what the other thread sees (the update value or some outdated "cached" value). It may also be that the other Thread did not yet write the update x
value back to the memory (only to L1/L2 Cache).I played a bit with the code and if I reduce the for
loop to 1000
iterations, I get both negative and positive values for the same code. This could be a hint that the JVM optimizes the code if we have many iterations. If we only have 1000 iterations, which is not that much i guess, the JVM may decide to run the code as it is written.
I suspect both problems have some influence on the result. For example, if you load x
, the processor will probably load that value into the L1 cache, and if you then execute the second operation, it may load that value directly from the L1 cache instead of the main memory. This could mean, that the 2nd operation causes no/less "lost updates".
But to really find out why that happens, I think you would to dig into the Java specification or even how the CPU handles such cases.
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