简体   繁体   中英

Non volatile double checked locking, is it possible?

Here is my singleton class.

Static instance field is not volatile thus reordering/visibility problem arises. To solve it instance val field is made final. Since instance is properly constructed its clients should always see val field initialized if they see instance at all.

    static class Singleton {
    private static Singleton instance;
    private final String val;
    public Singleton() { this.val = "foo"; }

    public static Singleton getInstance() {
        if (instance == null)
            synchronized (Singleton.class) {
                if(instance == null) {
                    instance = new Singleton();
            }
        }
        return instance;
    }
    public String toString() { return "Singleton: " + val; }
}

However there is another problem - I've got two unprotected reads of "instance" field which can be(?) reordered so that client may get null instead of real value:

public static Singleton getInstance() {
    Singleton temp = instance;
    if (instance != null) return temp;
    else { /* init singleton and return instance*/ }
}

To workaround this I feel like I can introduce local variable:

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

This seem to solve the problem since there is only one unprotected read of value so nothing really evil should happen. But... I've just modified the program flow without(almost?) changing its single threaded semantics. Does this mean that compiler can just undo my workaround since this transformation is safe and there is no way to make this code working without establishing proper happens-before relationship with volatile?

I'm not sure whether a reordering of reads of the same variable really may occur, but it's guaranteed that local variables are unaffected by other thread's activities. Even if such read reorderings do not happen, this guaranty is relevant for every variable which might be updated concurrently while you read it: if you read a value and store it into a local variable you can be sure that the value of the local variable does not suddenly change afterwards. Of course, if the value is a reference, that guaranty does not apply to the fields of the referenced object.

The relevant sentence can be found in the JLS §17.4.1 :

Local variables (§14.4), formal method parameters (§8.4.1), and exception handler parameters (§14.20) are never shared between threads and are unaffected by the memory model.

So the answer is no, a compiler is not allowed to undo your workaround of introducing a local variable.

The safest way to do lazy-initialisation singletons is to use another class to hold the single instance field and rely on the guarantees the Java language provides for class initialisation

public class Singleton {
  private static class Holder {
    static final Singleton instance = new Singleton();
  }

  public Singleton getInstance() {
    return Holder.instance;
  }
}

The Holder class will only be initialised (and thus the instance created) the first time getInstance() is called.

I don't think you have an issue from the beginning.

You use synchronized(Singleton.class) . Upon synchronized Java guarantees any read/write before this keyword are readily reflected into memory for involved variables. Since your Singleton instance is also declared at class level, any modification to it is readily visible from other class and is populated into main memory.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM