7

When doing lazy initialization of a static singleton in Java you can do this:

public class Bob {

    private static class SingletonWrapper {
        private static final Bob instance = new Bob();
    }

    public static Bob getInstance() {
       return SingletonWrapper.instance;
    }

}

Because the inner class SingletonWrapper is only loaded when first accessed the Bob() is not created until getInstance() is called.

My question is whether there are any similar tricks that can be used to do a lazy instantiation of a member variable in a non-static context.

public class Bob {

    // Clearly this doesn't work as not lazy
    private final InnerWrapper wrapper = new InnerWrapper();

    private class InnerWrapper {
        private final Jane jane = new Jane();
    }

    public Jane getJane() {
       return wrapper.jane;
    }

}

Is there any way we can have an instance of Jane within Bob and thread safely have the instance only created on demand without using double check locking or AtomicReference. Ideally the get method should remain as simple as the one in these examples but if that is not possible then the simplest and fastest possible (most efficient) execution of the get method would be ideal.

Tim B
  • 40,716
  • 16
  • 83
  • 128
  • @assylias As the comment says that would not do lazy initialization. As soon as you create a `Bob` you also always and immediately create a `Jane`. – Tim B Nov 12 '15 at 17:31
  • My bad I read too fast - any reasons why you don't want an AtomicReference? It would possibly perform better than the CHM option. – assylias Nov 12 '15 at 17:32
  • @assylias No specific reason. Basically I just want to know what my options are - considering how simple and effective the static wrapper trick is for lazy initialization of singletons it's a shame we can't come up with something similar for lazy initialization of members, – Tim B Nov 12 '15 at 17:34
  • And considering there is already two different ways to do it and the link to the really interesting lambda discussion I'm very interested to see what comes out of it :) – Tim B Nov 12 '15 at 17:35
  • @assylias Which `AtomicReference` method would you use here? I've looked over them and they all require side-effect free operators. There always seems to be a chance where `new Jane()` (the heavy part) would be executed multiple times. – Sotirios Delimanolis Nov 12 '15 at 17:54
  • @SotiriosDelimanolis `return ref.updateAndGet(j -> j == null ? new Jane() : j);` ? – assylias Nov 12 '15 at 18:00
  • @assylias That's what I thought at first, but looking at the implementation of `updateAndGet`, it first calls `get`, then that lambda implementation, then does a CAS. Two threads could call the lambda implementation with `null`. – Sotirios Delimanolis Nov 12 '15 at 18:06
  • @SotiriosDelimanolis I see what you mean - the javadoc says that the function may be called more than once. – assylias Nov 12 '15 at 18:08

2 Answers2

8

No, there are no synchronization rules for instantiating types like there are for initializing classes. You have to add them yourself. Whether you do it with double checked locking or some other mechanism is up to you.

Beginning with Java 8, I like to use ConcurrentHashMap#computeIfAbsent to achieve laziness.

class Bob {
    private final ConcurrentHashMap<String, Jane> instance = new ConcurrentHashMap<>(1);

    public Jane getJane() {
        return instance.computeIfAbsent("KEY", k -> new Jane()); // use whatever constant key
    }
}

There are also these solutions for lazy initializing without the thread-safety constraint. I was not able to adapt them cleanly for a multithreaded context.

As stated in the comments, double checked locking will always be faster than these solutions as they don't include all the fluff for hiding the implementation.

Community
  • 1
  • 1
Sotirios Delimanolis
  • 274,122
  • 60
  • 696
  • 724
  • The double checked locking would probably be fastest as there's no extra checks. You're doing at most what you need to do to get the instance. – Sotirios Delimanolis Nov 12 '15 at 17:18
  • 1
    Way better than my verbose solution using Guava, even though the logic is similar – Jean Logeart Nov 12 '15 at 17:19
  • I think the main difference is the use of a closure to cut out a bunch of the boilerplate. That would cut it down to a similar code length. – Tim B Nov 12 '15 at 17:21
  • Worth mentioning, though: any of these solutions is going to have the overhead of a map lookup, which will probably be more expensive than a checked locking approach you wrote yourself. – Louis Wasserman Nov 12 '15 at 17:41
  • @LouisWasserman Moved my previous comments to the answer. Anything else I should add? – Sotirios Delimanolis Nov 12 '15 at 17:57
2

You can use Guava's cache:

public class Bob {
    private final static Object KEY = new Object();

    private final Cache<Object, Jane> cache = 
        CacheBuilder.newBuilder()
                    .build(new CacheLoader<Object, Jane>() {
                               @Override
                               public Jane load() {
                                   return new Jane();
                               }
                           });

    public Jane getJane() {
        return cache.get(KEY);
    } 

}
Jean Logeart
  • 52,687
  • 11
  • 83
  • 118
  • This is not the main use case of a cache : automatic eviction. A concurrentHashMap will do the work and performs well than guava cache – Zava Dec 14 '15 at 11:10