Search code examples
javamultithreadingjvmvolatile

why the subthread can not see the changed static variable ? JMM or volatile


Here is an example of the usage of volatile keyword.

public class Test3{
    public static volatile boolean stop = false;// if volatile is not set, the loop will not stop
    public static void main(String[] args) throws InterruptedException{
        Thread thread = new Thread(){
            public void run() {
                int i=0;
                while(!stop){
                    i++;
                    // add this line
//                  System.out.println(i);
                    // or this block
//                  try {
//                      Thread.sleep(1);
//                  } catch (InterruptedException e) {
//                      e.printStackTrace();
//                  }
                }
            }
        };
        thread.start();
        Thread.sleep(2000);
        stop = true;
    }
}

It's easy to understand that if volatile is set, JVM should load the updated value from memory while checking its value, then the while loop can stop as expected. But question is, should not the static variable change at the same time? There may be some delay but eventually, this change should be detected. no? I've tested that if we add some print code or sleep code, this change can be detected? Can someone teach me why it likes that? Maybe it's about the JMM.


Solution

  • Time, in the sense of wall-clock time, has no meaning for the memory visibility. What matters is the synchronization order between synchronization actions. Reading and writing a non-volatile field is not a synchronization action, so in the absence of any other synchronization action, there is no ordering between them.

    So even if the main thread completed a year ago, so the write must have been done from the main thread’s perspective, the subthread may continue to run, running forever; the write has not happened from its perspective. It also doesn’t know that the main thread has terminated. Note that performing an action capable of detecting that the other thread has terminated, is a synchronization action that may establish an order.

    But since the actual program behavior also depends on the JIT compiler and optimizer, some code changes may have an impact, even if it is not guaranteed.

    E.g. inserting sleep does not imply any memory visibility:

    JLS §17.3. Sleep and Yield:

    It is important to note that neither Thread.sleep nor Thread.yield have any synchronization semantics. In particular, the compiler does not have to flush writes cached in registers out to shared memory before a call to Thread.sleep or Thread.yield, nor does the compiler have to reload values cached in registers after a call to Thread.sleep or Thread.yield.

    But it may stop the optimizer from considering the loop to be a hotspot that needs optimizations.

    When you insert a System.out.println statement, the internal synchronization of the PrintStream may have an impact on the overall memory visibility, though this effect also isn’t guaranteed, as the main thread does not synchronize on that PrintStream.

    By the way there isn’t even a guaranty that preemptive thread switching ever happens between threads of the same priority. Hence, it would be a valid execution, if a JVM tries to complete your subthread after start() has been called, before giving the CPU back to the main thread.

    In that execution scenario, with no sleep in the loop, the subthread would never give up the CPU, so stop would never be set to true, even if declared volatile. That would be another reason to avoid polling loops, though there might be no real life execution environment without preemptive thread switching anymore. Most of today’s execution environments even have multiple CPUs so not giving up a CPU won’t prevent other threads from executing.

    Still, to be formally correct, you should enforce an ordering between a write to the stop variable and the reading of the variable, like declaring the variable volatile and insert an action that may cause the thread to release the CPU eventually, when quit is still false.