Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can we get painless lazy loading of a Java member in a similar way to the way we can with static singletons?

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.

like image 881
Tim B Avatar asked Nov 12 '15 17:11

Tim B


2 Answers

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.

like image 144
Sotirios Delimanolis Avatar answered Oct 21 '22 20:10

Sotirios Delimanolis


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);
    } 

}
like image 25
Jean Logeart Avatar answered Oct 21 '22 22:10

Jean Logeart