Double-checked Locking (DCL) and how to fix it (ctd)

On the previous page, we looked at why double-checked locking is a problem. Now we look at correct idioms that can be used instead.

1. Just use synchronization, stoopid...

It sounds a bit glib, but one option is to just go ahead and use the synchronization that double-checked locking was trying to avoid. As Goetz et al point out, double-checked locking is "an idiom whose utility has largely passed". In other words, the notion that we must "avoid synchronization because it's slow" just isn't the case any more for uncontended synchronization on modern JVMs. So in effect, we could 'revert back' to the synchronized method that we were trying to avoid:

public class MyFactory {
  private static MyFactory instance;

  public static synchronized MyFactory getInstance() {
    if (instance == null)
      instance = new MyFactory();
    return instance;
  }

  private MyFactory() {}
}

2. Use the class loader

One of the most optimised and sophisticated pieces of synchronization in the JVM is actually that of the class loader. Every time we refer to a class, it must handle checking whether or not the class is loaded yet, and if it isn't, it must atomically load and initialise it. Every class can have a piece of static class initialisation code. So we could write this:

public class MyFactory {
  private static final MyFactory instance = new MyFactory();

  public static MyFactory getInstance() {
    return instance;
  }

  private MyFactory() {}
}

or, if we wanted or needed to handle exceptions in some special way, we can (and indeed must) define an explicit static block:

public class MyFactory {
  private static final MyFactory instance;

  static {
    try {
      instance = new MyFactory();
    } catch (IOException e) {
      throw new RuntimeException("Darn, an error's occurred!", e);
    }
  }

  public static MyFactory getInstance() {
    return instance;
  }

  private MyFactory() throws IOException {
    // read configuration files...
  }
}

Note that if you use the first variant, it actually gets turned by the compiler into something resembling the second. (I've heard people say things like "I don't like static initialisers because of (spurious reason X)", but there is no "magic" place to put variable initialisation in cases such as this!) However it is created, the JVM ensures that this static initialisation code is called exactly once, when the class is first referred to and loaded.

Using the class loader is generally my preferred way of dealing with lazy static initialisation. The code's nice and simple, and I don't think you can actually do it any more efficiently. The time that it won't work of course is if for some reason you need to pass in a parameter to getInstance().

3. Use DCL plus volatile

Double-checked locking is actually OK as of Java 5 provided that you make the instance reference volatile. So for example, if we needed to pass in a database connection to getInstance(), then this would be OK:

public class MyFactory {
  private static volatile MyFactory instance;

  public static MyFactory getInstance(Connection conn)
       throws IOException {
    if (instance == null) {
      synchronized (MyFactory.class) {
        if (instance == null)
          instance = new MyFactory(conn);
      }
    }
    return instance;  
  }

  private MyFactory(Connection conn) throws IOException {
    // init factory using the database connection passed in
  }
}

Note that this is OK as of Java 5 because the definition of volatile was specifically changed to make it OK. Accessing a volatile variable has the semantics of synchronization as of Java 5. In other words Java 5 ensures that the unsycnrhonized volatile read must happen after the write has taken place, and the reading thread will see the correct values of all fields on MyFactory.

4. Make all fields on the factory final

In Java 5, a change was made to the definition of final fields. Where the values of these fields are set in the constructor, the JVM ensures that these values are committed to main memory before the object reference itself. In other words, another thread that can "see" the object cannot ever see uninitialised values of its final fields. In that case, we wouldn't actually need to declare the instance reference as volatile.


If you enjoy this Java programming article, please share with friends and colleagues. Follow the author on Twitter for the latest news and rants.

Editorial page content written by Neil Coffey. Copyright © Javamex UK 2021. All rights reserved.