1

I am learning Java concurrency and know that the following singleton is not completely thread safe. A thread may get instance before it is initialized because of instructions reordering. A correct way to prevent this potential problem is to use volatile keyword.

public class DoubleCheckedLocking {
    private static Instance instance;
    public static Instance getInstance() {
        if (instance == null) {
            synchronized (DoubleCheckedLocking.class) {
                if (instance == null)
                    instance = new Instance();
            }
        }
        return instance;
    }
}

I tried to reproduce the potential problem without volatile keyword and wrote a demo to show that using the above code may cause a NullPointerException in multithreading environment. But I failed to find a way to explicitly let the Java compiler perform instructions reordering and my demo with the above singleton always works pretty well without any problems.

So my question is how to explicitly enable/disable Java compiler to reorder instructions or how to reproduce the problem without using volatile keyword in a double-checked locking singleton?

2
  • 3
    Not that it's not just the JIT that might do reordering; it's also things like CPU caches flushing across cores. Commented Jun 29, 2016 at 19:35
  • 1
    Note also that the fact that double-checked locking can fail says nothing about how likely you are to be able to observe a failure. Commented Jun 29, 2016 at 19:38

2 Answers 2

2

The dangerous thing here is not necessarily, that other threads may receive null as an answer from getInstance. The dangerous thing is, that they may observe an instance, which is not (yet) properly initialized.

To check this, add a few fields to your singleton, say:

class Singleton {

    private List<Object> members;

    private Singleton() {
        members = new ArrayList<>();
        members.addAll(queryMembers());
    }

    private Collection<Object> queryMembers() {
        return Arrays.asList("Hello", 1, 2L, "world", new Object());
    }

    public int size() {
        return members.size();
    }

    private static Singleton instance = null;

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (DoubleCheckedLocking.class) {
                if (instance == null)
                    instance = new Singleton();
            }
        }
        return instance;
    }
}

This is called "unsafe publication". Other threads may see the singleton instance partially initialized (i.e., the members field may still be null, or the list may be empty, or only partially filled, or worse: in an inconsistent state due to an object just being added).

In the example code above, no external caller of size should ever see a value different from 5, right? I didn't try it, but I wouldn't be surprised, if callers can observe different values, if the timing isn't right.

The reason for this is, that the compiler is allowed to translate

instance = new Singleton();

into something along the lines of

instance = allocate_instance(Singleton.class);   // pseudo-code
instance.<init>();

and thus, we have a window, in which instance is no longer null, but the actual object is not yet properly initialized.

The "Double-Checked Locking is Broken" Declaration gives an in-depth explanation of this.

Sign up to request clarification or add additional context in comments.

Comments

0

This is an excerpt from the Java Concurrency in Practice book:

Debugging tip: For server applications, be sure to always specify the -server JVM command line switch when invoking the JVM, even for development and testing. The server JVM performs more optimization than the client JVM, such as hoisting variables out of a loop that are not modified in the loop; code that might appear to work in the development environment (client JVM) can break in the deployment environment (server JVM). For example, had we "forgotten" to declare the variable asleep as volatile in Listing 3.4, the server JVM could hoist the test out of the loop (turning it into an infinite loop), but the client JVM would not. An infinite loop that shows up in development is far less costly than one that only shows up in production.

So you can give it a try. But there is no 100% sure way of enabling reordering.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.