Search code examples
javamultithreadingjava-memory-modeldouble-checked-lockingsynchronized-block

Does the latest JMM specify the synchronized block to be atomic to other threads even asynchronized ones?


As I went through an article about DOUBLE-CHECKED LOCKING on http://www.javaworld.com/article/2074979/java-concurrency/double-checked-locking--clever--but-broken.html , I encounter a comment which says " It should be noted that DCL might, in fact, work on some versions of some JVMs -- as few JVMs actually implement the JMM properly. " So I infer from it that JMM specify the synchronized block to be atomic even to blocks not synchronized in other threads. Am I right? (I tried to read the JMM on oracle's website, but it was too abstract, and I gave up.)


Solution

  • First of all, please note that Brian Goetz has written this article in 2001. The information that is portrayed in this article is no longer accurate after the implementation of JSR-133, the revised memory model. What is however true is that the example DCL of the article is broken:

    class SomeClass {
    
      private Resource resource = null;
    
      public Resource getResource() {
        if (resource == null) {
          synchronized (this) {
            if (resource == null) 
              resource = new Resource();
          }
        }
        return resource;
      }
    }
    

    Using the above code, the resource field might be observed to be not null while the instance's constructor was not yet fully executed. The problem is that a constructor is not guaranteed to be exectured before the field assignment as the JVM can apply code optimizations. The constructor call should therefore rather be seen as (in pseudo-code):

    resource = alloc Resource;
    resource.new();
    

    With this information, one can see how the initial check resource == null can yield false for another thread even before new is called what exposes the incomplete instance to another thread. This other thread would never enter the synchronized block and not wait for the completion of the constructor call.

    In today's Java, it is however sufficient to makr the resource field to be volatile. In this case, DCL does work and is even quite efficient because reading a volatile field is not too expensive on most hardware. Alexey Shipilev has discussed the performance implications of safe, lazy publication in detail. DCL with volatile is a common pattern today, for example being used by Scala for its lazy fields.

    But to answer your actual question: basically all implementations of the JVM implement the memory model in a looser fashion than its specification. Therefore, the non-volatile DCL might just work on many machines despite the improper synchronization because of an implementation detail. You should however never code against the implementation but always against the specification. Otherwise, your code might fail only sometimes and only on some machines what is a terrible bug to trace! This has nothing to do with the synchronized block being atomic, it solely relates to how your VM executes your code where the constructor might incidentally always be executed before publicizing your instance to the resource field.