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.