Close

Java Memory Model - Reordering Problem

[Updated: Oct 21, 2018, Created: Jul 8, 2016]

As stated in a previous tutorial: without proper synchronization, Java Memory Model can allow actions to appear executing in different orders from the perspective of different threads which sometimes produces the unexpected results. There might be various reasons of why operations might appear to execute out of order, but they all can be put into the general category of reordering problem.



In above example Thread1 writes to two variables 'a' and 'flg' and thread2 conditionally prints value of 'a' if 'flg' is true.

A programmer, of course, will make logical decisions based on the code order he/she is writing. Given thread1's code order, the programmer will never expect that thread2 will print a = 2 as the 'flg' is set to true after changing the value of 'a' to 1.

/**
* Without any reordering prevention
*/
public class ReorderExample {
  private int a = 2;
  private boolean flg = false;

  public void method1() {
      a = 1;
      flg = true;
  }

  public void method2() {
      if (flg) {
          //2 might be printed out on some JVM/machines
          System.out.println("a = " + a);
      }
  }

  public static void main(String[] args) {
      for (int i = 0; i < 100; i++) {
          ReorderExample reorderExample = new ReorderExample();
          Thread thread1 = new Thread(() -> {
              reorderExample.method1();
          });
          Thread thread2 = new Thread(() -> {
              reorderExample.method2();
          });
          thread1.start();
          thread2.start();
      }
  }
}

Preventing reordering problem?

The reasons of reordering problems may vary. It might be delayed write (due to any reasons, including how low level OS mechanism handles threads) to main memory which makes the code appears to be reordered or might be because of real code ordering as a results of JIT compiler/processor code optimization.

Java Memory Model doesn't require a programmer to figure out the real low level reasons because those reasons vary on different JIT compilers and on different machine architectures. It requires a programmer to recognize the situations where reordering might happen and do proper synchronization.

In above example declaring both variables as volatile won't solve the problem, the reason is volatile just solves the visibility problem. We have to use synchronized blocks (locks) to prevent the reordering.


Output:

public class ReorderPrevention {
  private int a = 2;
  private boolean flg = false;

  public synchronized void method1() {
      a = 1;
      flg = true;
  }

  public synchronized void method2() {
      if (flg) {
          System.out.println("a = " + a);
      }
  }

  public static void main(String[] args) {
      for (int i = 0; i < 100; i++) {
          ReorderPrevention reorderExample = new ReorderPrevention();
          Thread thread1 = new Thread(() -> {
              reorderExample.method1();
          });
          Thread thread2 = new Thread(() -> {
              reorderExample.method2();
          });
          thread1.start();
          thread2.start();
      }
  }
}

Synchronizing the shared data access across threads achieves two things:

  • Mutual exclusion of two thread execution over synchronized block. This achieves atomicity. A threads is not able to see incomplete writes (involving multiple variables) of other threads.
  • Flushing of local thread memory to main memory at the time when the lock is released (at the end of synchronized block).

See Also