Double-checked locking without volatile
Asked Answered
T

5

30

I read this question about how to do Double-checked locking:

// Double-check idiom for lazy initialization of instance fields
private volatile FieldType field;
FieldType getField() {
    FieldType result = field;
    if (result == null) { // First check (no locking)
        synchronized(this) {
            result = field;
            if (result == null) // Second check (with locking)
                field = result = computeFieldValue();
        }
    }
    return result;
}

My aim is to get lazy-loading a field (NOT a singleton) work without the volatile attribute. The field object is never changed after initialization.

After some testing my final approach:

    private FieldType field;

    FieldType getField() {
        if (field == null) {
            synchronized(this) {
                if (field == null)
                    field = Publisher.publish(computeFieldValue());
            }
        }
        return fieldHolder.field;
    }



public class Publisher {

    public static <T> T publish(T val){
        return new Publish<T>(val).get();
    }

    private static class Publish<T>{
        private final T val;

        public Publish(T val) {
            this.val = val;
        }

        public T get(){
            return val;
        }
    }
}

The benefit is possibly faster access time due to not needing volatile, while still keeping the simplicity with the reusable Publisher class.


I tested this using jcstress. SafeDCLFinal worked as expected while UnsafeDCLFinal was inconsistent (as expected). At this point im 99% sure it works, but please, prove me wrong. Compiled with mvn clean install -pl tests-custom -am and run with java -XX:-UseCompressedOops -jar tests-custom/target/jcstress.jar -t DCLFinal. Testing code below (mostly modified singleton testing classes):

/*
 * SafeDCLFinal.java:
 */

package org.openjdk.jcstress.tests.singletons;

public class SafeDCLFinal {

    @JCStressTest
    @JCStressMeta(GradingSafe.class)
    public static class Unsafe {
        @Actor
        public final void actor1(SafeDCLFinalFactory s) {
            s.getInstance(SingletonUnsafe::new);
        }

        @Actor
        public final void actor2(SafeDCLFinalFactory s, IntResult1 r) {
            r.r1 = Singleton.map(s.getInstance(SingletonUnsafe::new));
        }
    }

    @JCStressTest
    @JCStressMeta(GradingSafe.class)
    public static class Safe {
        @Actor
        public final void actor1(SafeDCLFinalFactory s) {
            s.getInstance(SingletonSafe::new);
        }

        @Actor
        public final void actor2(SafeDCLFinalFactory s, IntResult1 r) {
            r.r1 = Singleton.map(s.getInstance(SingletonSafe::new));
        }
    }


    @State
    public static class SafeDCLFinalFactory {
        private Singleton instance; // specifically non-volatile

        public Singleton getInstance(Supplier<Singleton> s) {
            if (instance == null) {
                synchronized (this) {
                    if (instance == null) {
//                      instance = s.get();
                        instance = Publisher.publish(s.get(), true);
                    }
                }
            }
            return instance;
        }
    }
}

/*
 * UnsafeDCLFinal.java:
 */

package org.openjdk.jcstress.tests.singletons;

public class UnsafeDCLFinal {

    @JCStressTest
    @JCStressMeta(GradingUnsafe.class)
    public static class Unsafe {
        @Actor
        public final void actor1(UnsafeDCLFinalFactory s) {
            s.getInstance(SingletonUnsafe::new);
        }

        @Actor
        public final void actor2(UnsafeDCLFinalFactory s, IntResult1 r) {
            r.r1 = Singleton.map(s.getInstance(SingletonUnsafe::new));
        }
    }

    @JCStressTest
    @JCStressMeta(GradingUnsafe.class)
    public static class Safe {
        @Actor
        public final void actor1(UnsafeDCLFinalFactory s) {
            s.getInstance(SingletonSafe::new);
        }

        @Actor
        public final void actor2(UnsafeDCLFinalFactory s, IntResult1 r) {
            r.r1 = Singleton.map(s.getInstance(SingletonSafe::new));
        }
    }

    @State
    public static class UnsafeDCLFinalFactory {
        private Singleton instance; // specifically non-volatile

        public Singleton getInstance(Supplier<Singleton> s) {
            if (instance == null) {
                synchronized (this) {
                    if (instance == null) {
//                      instance = s.get();
                        instance = Publisher.publish(s.get(), false);
                    }
                }
            }
            return instance;
        }
    }
}

/*
 * Publisher.java:
 */

package org.openjdk.jcstress.tests.singletons;

public class Publisher {

    public static <T> T publish(T val, boolean safe){
        if(safe){
            return new SafePublish<T>(val).get();
        }
        return new UnsafePublish<T>(val).get();
    }

    private static class UnsafePublish<T>{
        T val;

        public UnsafePublish(T val) {
            this.val = val;
        }

        public T get(){
            return val;
        }
    }

    private static class SafePublish<T>{
        final T val;

        public SafePublish(T val) {
            this.val = val;
        }

        public T get(){
            return val;
        }
    }
}

Tested with java 8, but should work at least with java 6+. See docs


But I wonder if this would work:

    // Double-check idiom for lazy initialization of instance fields without volatile
    private FieldHolder fieldHolder = null;
    private static class FieldHolder{
        public final FieldType field;
        FieldHolder(){
            field = computeFieldValue();
        }
    }

    FieldType getField() {
        if (fieldHolder == null) { // First check (no locking)
            synchronized(this) {
                if (fieldHolder == null) // Second check (with locking)
                    fieldHolder = new FieldHolder();
            }
        }
        return fieldHolder.field;
    }

Or maybe even:

    // Double-check idiom for lazy initialization of instance fields without volatile
    private FieldType field = null;
    private static class FieldHolder{
        public final FieldType field;

        FieldHolder(){
            field = computeFieldValue();
        }
    }

    FieldType getField() {
        if (field == null) { // First check (no locking)
            synchronized(this) {
                if (field == null) // Second check (with locking)
                    field = new FieldHolder().field;
            }
        }
        return field;
    }

Or:

    // Double-check idiom for lazy initialization of instance fields without volatile
    private FieldType field = null;

    FieldType getField() {
        if (field == null) { // First check (no locking)
            synchronized(this) {
                if (field == null) // Second check (with locking)
                    field = new Object(){
                        public final FieldType field = computeFieldValue();
                    }.field;
            }
        }
        return field;
    }

I belive this would work based on this oracle doc:

The usage model for final fields is a simple one: Set the final fields for an object in that object's constructor; and do not write a reference to the object being constructed in a place where another thread can see it before the object's constructor is finished. If this is followed, then when the object is seen by another thread, that thread will always see the correctly constructed version of that object's final fields. It will also see versions of any object or array referenced by those final fields that are at least as up-to-date as the final fields are.

Trinl answered 26/4, 2015 at 20:54 Comment(11)
Even if it worked, what would be the benefit?Jumbuck
@Jumbuck Possibly faster access to the field do to omitting volatile.Trinl
It won't work without volatile. It's all about visibility, not atomicity. If field is not volatile, write operations to field of one thread might never be visible to read operations of another thread. It's pointless to fiddle around - thread safe singleton is already solved.Jumbuck
@Jumbuck I was gonna mention that double checked locking is a broken system (eager initialization > lazy), but he didn't mention this was a singletonSideboard
1. This question has nothing to do with singletons. 2. "write operations to field of one thread might never be visible to read operations of another thread" - thats not true for final fields.Trinl
@VinceEmigh see the "Under the new Java Memory Model" part in your link.Trinl
@VinceEmigh You're right! It seems my brain automatically wires double checked locking with singletons...Jumbuck
@Trinl The outer field is not final in your case.Jumbuck
"If Helper is an immutable object, such that all of the fields of Helper are final, then double-checked locking will work without having to use volatile fields. The idea is that a reference to an immutable object (such as a String or an Integer) should behave in much the same way as an int or float; reading and writing references to immutable objects are atomic." - quote from the page @VinceEmigh linked - FieldHolder is an immutable class (not strictly, but close enough)Trinl
@JohnVint because its NOT singleton.Trinl
@Trinl I think docs.oracle.com/javase/specs/jls/se7/html/jls-17.html Example 17.5-1. final Fields In The Java Memory Model shows what will happen.Beechnut
R
30

First things first: what you are trying to do is dangerous at best. I am getting a bit nervous when people try to cheat with finals. Java language provides you with volatile as the go-to tool to deal with inter-thread consistency. Use it.

Anyhow, the relevant approach is described in "Safe Publication and Initialization in Java" as:

public class FinalWrapperFactory {
  private FinalWrapper wrapper;

  public Singleton get() {
    FinalWrapper w = wrapper;
    if (w == null) { // check 1
      synchronized(this) {
        w = wrapper;
        if (w == null) { // check2
          w = new FinalWrapper(new Singleton());
          wrapper = w;
        }
      }
    }
    return w.instance;
  }

  private static class FinalWrapper {
    public final Singleton instance;
    public FinalWrapper(Singleton instance) {
      this.instance = instance;
    }
  }
}

It layman's terms, it works like this. synchronized yields the proper synchronization when we observe wrapper as null -- in other words, the code would be obviously correct if we drop the first check altogether and extend synchronized to the entire method body. final in FinalWrapper guarantees iff we saw the non-null wrapper, it is fully constructed, and all Singleton fields are visible -- this recovers from the racy read of wrapper.

Note that it carries over the FinalWrapper in the field, not the value itself. If instance were to be published without the FinalWrapper, all bets would be off (in layman terms, that's premature publication). This is why your Publisher.publish is disfunctional: just putting the value through final field, reading it back, and publishing it unsafely is not safe -- it's very similar to just putting the naked instance write out.

Also, you have to be careful to make a "fallback" read under the lock, when you discover the null wrapper, and use its value. Doing the second (third) read of wrapper in return statement would also ruin the correctness, setting you up for a legitimate race.

EDIT: That entire thing, by the way, says that if the object you are publishing is covered with final-s internally, you may cut the middleman of FinalWrapper, and publish the instance itself.

EDIT 2: See also, LCK10-J. Use a correct form of the double-checked locking idiom, and some discussion in comments there.

Reserve answered 5/5, 2015 at 9:9 Comment(2)
according to this en.wikipedia.org/wiki/Double-checked_locking#Usage_in_Java your solution has to have 'volatile' on the field for jdk5+ for jdk4 it is broken.Pullet
can I ask the all mighty Alexey to help a few mere mortals here please?Sympathizer
T
8

In short

The version of the code without the volatile or the wrapper class is dependent on the memory model of the underlying operating system that the JVM is running on.

The version with the wrapper class is a known alternative known as the Initialization on Demand Holder design pattern and relies upon the ClassLoader contract that any given class is loaded at most once, upon first access, and in a thread-safe way.

The need for volatile

The way developers think of code execution most of the time is that the program is loaded into main memory and directly executed from there. The reality, however, is that there are a number of hardware caches between main memory and the processor cores. The problem arises because each thread might run on separate processors, each with their own independent copy of the variables in scope; while we like to logically think of field as a single location, the reality is more complicated.

To run through a simple (though perhaps verbose) example, consider a scenario with two threads and a single level of hardware caching, where each thread has their own copy of field in that cache. So already there are three versions of field: one in main memory, one in the first copy, and one in the second copy. I'll refer to these as fieldM, fieldA, and fieldB respectively.

  1. Initial state
    fieldM = null
    fieldA = null
    fieldB = null
  2. Thread A performs the first null-check, finds fieldA is null.
  3. Thread A acquires the lock on this.
  4. Thread B performs the first null-check, finds fieldB is null.
  5. Thread B tries to acquire the lock on this but finds that it's held by thread A. Thread B sleeps.
  6. Thread A performs the second null-check, finds fieldA is null.
  7. Thread A assigns fieldA the value fieldType1 and releases the lock. Since field is not volatile this assignment is not propagated out.
    fieldM = null
    fieldA = fieldType1
    fieldB = null
  8. Thread B awakes and acquires the lock on this.
  9. Thread B performs the second null-check, finds fieldB is null.
  10. Thread B assigns fieldB the value fieldType2 and releases the lock.
    fieldM = null
    fieldA = fieldType1
    fieldB = fieldType2
  11. At some point, the writes to cache copy A are synched back to main memory.
    fieldM = fieldType1
    fieldA = fieldType1
    fieldB = fieldType2
  12. At some later point, the writes to cache copy B are synched back to main memory overwriting the assignment made by copy A.
    fieldM = fieldType2
    fieldA = fieldType1
    fieldB = fieldType2

As one of the commenters on the question mentioned, using volatile ensures writes are visible. I don't know the mechanism used to ensure this -- it could be that changes are propagated out to each copy, it could be that the copies are never made in the first place and all accesses of field are against main memory.

One last note on this: I mentioned earlier that the results are system dependent. This is because different underlying systems may take less optimistic approaches to their memory model and treat all memory shared across threads as volatile or may perhaps apply a heuristic to determine whether a particular reference should be treated as volatile or not, though at the cost of performance of synching to main memory. This can make testing for these problems a nightmare; not only do you have to run against a enough large sample to try to trigger the race condition, you might just happen to be testing on a system which is conservative enough to never trigger the condition.

Initialization on Demand holder

The main thing I wanted to point out here is that this works because we're essentially sneaking a singleton into the mix. The ClassLoader contract means that while there can many instances of Class, there can be only a single instance of Class<A> available for any type A, which also happens to be loaded on first when first reference / lazily-initialized. In fact, you can think of any static field in a class's definition as really being fields in a singleton associated with that class where there happens to be increased member access privileges between that singleton and instances of the class.

Tomkin answered 7/5, 2015 at 3:30 Comment(4)
I'm afraid that you're wrong. In the point 7 - the field indeed might not be volatile, but releasing a lock guarantess a memory visibility, so the assignment is propagated out and Thread B will be aware of it.Colicroot
@ZZ5 : does it mean that volatile is not needed in DCL?Deficiency
@mav3n Yep, it's not needed in DCL. In general DCL itself is a bad idea and it was introduced in early versions of Java, because even uncontended synchronized access was kinda expensive, however that is no longer true. For more information there's a chapter dedicated DCL in Java Concurrency in PracticeColicroot
@ZZ5 Volatile is required to put in a memory barrier, ensuring that the check and the set of the reference wasn't reordered. Double-check locking in versions of Java prior to 1.5 were broken specifically because of the semantics of volatile were incorrect. This is a motivating factor for the introduction of the memory model in 1.5 via JSR 133. After that fix then double-check locking works fine.Tomkin
H
2

Quoting The "Double-Checked Locking is Broken" Declaration mentioned by @Kicsi, the very last section is:

Double-Checked Locking Immutable Objects

If Helper is an immutable object, such that all of the fields of Helper are final, then double-checked locking will work without having to use volatile fields. The idea is that a reference to an immutable object (such as a String or an Integer) should behave in much the same way as an int or float; reading and writing references to immutable objects are atomic.

(emphasis is mine)

Since FieldHolder is immutable, you indeed don't need the volatile keyword: other threads will always see a properly-initialized FieldHolder. As far as I understand it, the FieldType will thus always be initialized before it can be accessed from other threads through FieldHolder.

However, proper synchronization remains necessary if FieldType is not immutable. By consequent I'm not sure you would have much benefit from avoiding the volatile keyword.

If it is immutable though, then you don't need the FieldHolder at all, following the above quotation.

Hillock answered 30/4, 2015 at 15:25 Comment(4)
FieldType is not necessarily immutable (it may contain non-final fields), although we can assume it doesn't change after construction, or if it does, thread-safety of that change is not our concern.Trinl
You are right, the immutability of FieldType probably does not affect the validity of your solution. I updated my answer accordingly.Hillock
Note that the linked article is only for versions of Java prior to 1.5; as the article itself indicates the memory model in Java 1.5 onward fixes the described behavior.Tomkin
@Tomkin the linked article has a specific section for 1.5 and later, from which is coming the quoted text.Hillock
P
1

Using Enum or nested static class helper for lazy initialization otherwise just use static initialization if the initialization won't take much cost (space or time).

public enum EnumSingleton {
    /**
     * using enum indeed avoid reflection intruding but also limit the ability of the instance;
     */
    INSTANCE;

    SingletonTypeEnum getType() {
        return SingletonTypeEnum.ENUM;
    }
}

/**
 * Singleton:
 * The JLS guarantees that a class is only loaded when it's used for the first time
 * (making the singleton initialization lazy)
 *
 * Thread-safe:
 * class loading is thread-safe (making the getInstance() method thread-safe as well)
 *
 */
private static class SingletonHelper {
    private static final LazyInitializedSingleton INSTANCE = new LazyInitializedSingleton();
}

The "Double-Checked Locking is Broken" Declaration

With this change, the Double-Checked Locking idiom can be made to work by declaring the helper field to be volatile. This does not work under JDK4 and earlier.

  class Foo {
        private volatile Helper helper = null;
        public Helper getHelper() {
            if (helper == null) {
                synchronized(this) {
                    if (helper == null)
                        helper = new Helper();
                }
            }
            return helper;
        }
    }
Perdue answered 28/3, 2019 at 3:50 Comment(0)
V
-1

No, this would not work.

final does not guarantee the visibility between threads that volatile does. The Oracle doc you quoted says that other threads will always see a correctly constructed version of an object's final fields. final guarantees that all final fields have been constructed and set by the time an objects constructor has finished running. So if object Foo contains a final field bar, bar is guaranteed to be constructed by the time Foo's constructor has finished.

The object referenced by a final field is still mutable though, and writes to that object may not be correctly visible across different threads.

So in your examples, other threads are not guaranteed to see the FieldHolder object that has been created and may create another, or if any modifications happen to the state of the FieldType object, it is not guaranteed that other threads will see these modifications. The final keyword is only guaranteeing that once the other threads do see the FieldType object, its constructor has been called.

Valle answered 26/4, 2015 at 21:48 Comment(4)
"The object referenced by a final field is still mutable though, and writes to that object may not be correctly visible across different threads." - the object is not modified later (edited my question) "other threads are not guaranteed to see the FieldHolder object that has been created and may create another" - synchronized takes care of thatTrinl
If you aren't planning on modifying it, then it should work and you shouldn't need your wrapper class with the final keyword as I don't believe it gains you anything over just instantiating FieldType in the synchronized block since the wrapper class isn't (and can't be) final.Valle
Please read Double-Checked Locking is Broken - it does not simply work without setting "field" to volatile.Trinl
You are correct. I realized that shortly after posting this comment. Sorry about that.Valle

© 2022 - 2024 — McMap. All rights reserved.