Search code examples
javaconcurrencythread-safetysynchronizedvolatile

how to build a lazy initializing thread safe wrapper for an object reference


I want to implement a wrapper class. The class's only public facing stuff are:

  • A constructor that takes logic to create an instance of the wrapped class. Such as Supplier<WrappedType>, perhaps.
  • A method that gets the instance of the wrapped class.

With the following rule on its behaviour: the logic that creates the wrapped class can have side effects and must only be called once. And, obviously, the getter method should always actually return the same wrapped class instance, which should actually be the result of running the logic that was passed in to the constructor.

I think I have this code that does what I want, but I'm not sure how to test if it is guaranteed to work or not or if there is a better way to do it.

package foo;

import java.util.function.Supplier;

public final class ConcurrentLazyContainer<A> {
    private Supplier<A> supplier;
    private A value;

    public ConcurrentLazyContainer(Supplier<A> supplier) {
        this.supplier = supplier;
        value = null;
    }

    public synchronized A get() {
        if (value == null) {
            value = supplier.get();
            supplier = null;
        }

        return value;
    }
}

Does just using synchronized get me all the way to what I want here? Do my fields need to be volatile too, perhaps?

I wrote a test that spins up new threads calling the same wrapper but it doesn't appear to me that supplier gets called more than once, which is weird because I don't really understand why volatile wouldn't be necessary here.


Solution

  • The comments on the question are right: if you only access value within the synchronized method, then you don't need it to be volatile as well. However, in some cases, you may be able to improve performance with double-checked locking.

    public final class Lazy<T> {
    
      private final Supplier<? extends T> initializer;
      private volatile T value;
    
      public Lazy(Supplier<? extends T> initializer) {
        this.initializer = initializer;
      }
    
      public T get() {
        T result = this.value;
        if (result == null) {
          synchronized (this) {
            result = this.value;
            if (result == null) {
              this.value = result = this.initializer.get();
            }
          }
        }
        return result;
      }
    
    }
    

    This code is based on some examples shown in Effective Java and Java Concurrency in Practice. Note that this code checks twice to see if result is null, once outside the synchronized block, and once inside. The advantage to this is that you won't need to synchronize if the value already exists. Note that with this strategy, value must be volatile because it is being accessed outside of the synchronized block.