4

I have two unsynchronized threads in a tight loop, incrementing a global variable X times (x=100000).

The correct final value of the global should be 2*X, but since they are unsynchronized it will be less, empirically it is typically just a bit over X

However, in all the test runs the value of global was never under X .

Is it possible for the final result to be less than x ( less than 100000 )?

public class TestClass {
    static int global;
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread( () -> {  for(int i=0; i < 100000; ++i) {     TestClass.global++;  }  });
        Thread t2 = new Thread( () -> { for(int i=0; i < 100000; ++i) {     TestClass.global++;  }  });
        t.start(); t2.start();
        t.join(); t2.join();
        System.out.println("global = " + global);
    }
}
curiousguy
  • 8,038
  • 2
  • 40
  • 58
Gonen I
  • 5,576
  • 1
  • 29
  • 60
  • Relevant: https://docs.oracle.com/javase/specs/jls/se11/html/jls-17.html#jls-17.3 – VGR Dec 16 '19 at 22:22

1 Answers1

5

Just imagine the following scenario:

  • Thread A reads the initial value 0 from global
  • Thread B performs 99999 updates on global
  • Thread A writes 1 to global
  • Thread B reads 1 from global
  • Thread A perform its remaining 99999 updates on global
  • Thread B writes 2 to global

Then, both threads completed but the resulting value is 2, not 2 * 100000, nor 100000.

Note that the example above just uses a bad timing without letting any thread perceive reads or writes of the other thread out-of-order (which would be permitted in the absence of synchronization) nor missing updates (which also would be permitted here).

In other words, the scenario shown above would even be possible when the global variable was declared volatile.

It is a common mistake to reason about reads and writes and their visibility, but implicitly assuming a particular timing for the execution of the thread’s code. But it is not guaranteed that these threads run side-by-side with a similar instruction timing.

But that may still happen in your test scenarios, so they’re not revealing the other possible behavior. Also, some legal behavior may never occur on a particular hardware or with a particular JVM implementation, while the developer still has to account for it. It might be very well, that the optimizer replaces the incrementing loop by the equivalent of global += 100000 which rarely exhibits in-between values in such a test, but the insertion of some other nontrivial operation into the loop body could change the behavior entirely.

Holger
  • 285,553
  • 42
  • 434
  • 765