1

The question actually refers to a different question, which was closed as duplicate because it was probably not well formulated.

What would be an effective alternative lazy initialization idiom instead of double-checked locking for this code sample (in a multithreaded environment):

public class LazyEvaluator {
  private final Object state;
  private volatile LazyValue lazyValue;

  public LazyEvaluator(Object state) {
      this.state = state;
  }

  public LazyValue getLazyValue() {
      if (lazyValue == null) {
          synchronized (this) {
              if (lazyValue == null) {
                  lazyValue = new LazyValue(someSlowOperationWith(state), 42);
              }
          }  
      }
      return lazyValue;
  }

  public static class LazyValue {
      private String name;
      private int value;

      private LazyValue(String name, int value) {
          this.name = name;
          this.value = value;  
      }

      private String getName() {
          return name;
      }

      private int getValue() {
          return value;
      }

  }

}

EDIT Updated to include a slow operation and added explicit mention about multithreaded environment

Community
  • 1
  • 1
AngryJuice
  • 61
  • 1
  • 6

3 Answers3

3

If I understand you, then you could change this

public LazyValue getLazyValue() {
  if (lazyValue == null) {
    synchronized (this) {
      if (lazyValue == null) {
        lazyValue = new LazyValue(state.toString());
      }
    }  
  }
  return lazyValue;
}

to this

public synchronized LazyValue getLazyValue() {
  if (lazyValue == null) {
    lazyValue = new LazyValue(state.toString());
  }
  return lazyValue;
}

But it's only necessary pre-Java 5 (which doesn't support acquire/release semantics for volatile) and if mulitple threads might access the same instance of your LazyEvaluator. If each thread has a thread-local instance then you don't need to synchronize.

Elliott Frisch
  • 198,278
  • 20
  • 158
  • 249
  • Just added mention about multithreaded environment. The problem with synchronized is that it may becomes a bottleneck under contention even after we already initialized the LazyValue and that's why the double-checked locking was used. – AngryJuice Aug 13 '14 at 15:20
3

The simplest solution would be

public LazyValue getLazyValue() {
    return new LazyValue(state.toString(), 42);
}

as LazyValue is a trivial object which is not worth to be remembered at all.


If an expensive computation is involved you can turn the LazyValue into a true immutable object by declaring its fields final:

public static class LazyValue {
    private final String name;
    private final int value;
// …

this way you could publish the instance even through a data race:

// with lazyValue even not being volatile
public LazyValue getLazyValue() {
    return lazyValue!=null? lazyValue:
        (lazyValue=new LazyValue(state.toString(), 42));
}

In this case the value might be calculated multiple times in the unlikely case that multiple threads access it concurrently but once a thread sees a non-null value, it will be a correctly initialized value due to the final field initialization guaranty.


If the calculation is so expensive that even an unlikely concurrent calculation must be avoided, then simply declare getLazyValue() synchronized as its overhead will be negligible compared to the calculation that will be saved.


Finally, if you really encounter a scenario were the computation is so heavy, that overlapping concurrent computation must be avoided at all cost but profiling shows that later-on synchronization is a bottleneck, you might have encountered one of the very rare cases were double-checked locking could be an option (really rare).

In this case, there’s still an alternative to your question’s code. Combine the DCL with my suggestion above of declaring all LazyValue’s fields as final and make the lazyValue holder field non-volatile. That way you can even save the volatile read after the lazy value has been constructed. However, I still say, it should be really rarely needed.

Maybe that’s the non-technical reason why DCL has so much negative reputation: it’s appearance in discussions (or on StackOverflow) is way out of all proportion to its real need.

Holger
  • 285,553
  • 42
  • 434
  • 765
  • The LazyValue may be trivial, but hard to calculate - that's the reason why it made "lazy". – AngryJuice Aug 13 '14 at 15:23
  • I agree about negligible overhead but only at first invocation. Further ones will be multiple times slower than they should. – AngryJuice Aug 13 '14 at 15:30
  • 1
    @AngryJuice: do you have any prove for your “multiple times slower than they should”? The cost of synchronization are often over-estimated. – Holger Aug 13 '14 at 15:37
  • It's hard to give the precise estimation, but as an example: http://www.cs.umd.edu/~pugh/java/memoryModel/DCL-performance.html – AngryJuice Aug 13 '14 at 15:41
  • It actually uses old version of Java, but I doubt something has changed so much that synchronization became very cheap, even with stuff like biased locking etc. Contention and fat locks are expensive. I can try to find something newer, but not sure I will find a good benchmark. – AngryJuice Aug 13 '14 at 15:47
  • 1
    @AngryJuice: you are wrong, these values are *way out of date*. They are older than the Java Memory Model we are talking about. On these JVMs, even the “fixed DCL” using `volatile` doesn’t work reliably. By the way, everything you find on these pages of Doug Lea and Bill Pugh has already been incorporated into the today’s JVM development as these two are the guys mostly responsible for the JMM as we know it today… – Holger Aug 13 '14 at 15:58
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/59285/discussion-between-angryjuice-and-holger). – AngryJuice Aug 13 '14 at 15:59
1

Well, "effective alternative lazy initialization idiom" leaves a lot of flexibility, so I'll put my two cents in the ring by noting that this might be a good place to apply a library. In particular, Guava. https://code.google.com/p/guava-libraries/

// You have some long method call you want to make lazy
MyValue someLongMethod(int input) { ... }
// So you wrap it in a supplier so it's lazy
Supplier<MyValue> lazy = new Supplier<MyValue>() { 
  public MyValue get() {
    return someLongMethod(2);
  }
}
// and you want it only to be called once ...
Supplier<MyValue> cached = Suppliers.memoize(lazy);
// ... and someLongMethod won't actually be called until
cached.get();

Double-checked-locking is is used (properly) by the Suppliers class. AS far as idioms go, Supplier is certainly effective and quite popular --java.util.function.Supplier came in Java 8.

Good luck.

Darren Gilroy
  • 2,071
  • 11
  • 6