Saturday, 6 August 2016

Thread safe lazy initialization using proxy

Sometimes we need to defer the object creation until it is needed.
The reasons may be various like creation is time/resource consuming (database connection etc.) and we want to speed up application startup.

This is a use case for the lazy initialization pattern.

We have a a following service interface and implementation that returns current time:

public interface Service {
LocalDateTime getTime();
}
public class DefaultService implements Service {
private static final Logger logger = LoggerFactory.getLogger(DefaultService.class.getName());
public DefaultService() {
logger.debug("Initializing service");
}
@Override
public LocalDateTime getTime() {
return now();
}
}
view raw Service.java hosted with ❤ by GitHub

Let's imagine that the initialization is very costly.

Simple lazy initialization could look as follows:
public class LazyInitService implements Service {
private Service service;
@Override
public LocalDateTime getTime() {
initializeIfNecessary();
return service.getTime();
}
private void initializeIfNecessary() {
if (service == null) {
service = new DefaultService();
}
}
}
We're creating DefaultService only once when we're invoking getTime method.
This solution is fine in single threaded environment (we will cover multi threaded environment later).

The problem arises when the service  does not have few methods but a lot of them (it may happen when you're using some legacy or not well designed library).
Such implementation would look awful in this situation - you would have to implement every method and try to initialize the object in all of them.

In that case we can create a proxy object that would lazy initialization for each method invocation.
To create such proxy we need to implement java.lang.reflect.InvocationHandler interface.
There are some pitfalls the we need to overcome (more info here), that's why it's easier to use Guava's AbstractInvocationHandler:
public class LazyInitWithProxyServiceFactory {
public static Service createService() {
return Reflection.newProxy(Service.class, new LazyInitInvocationHandler());
}
private static class LazyInitInvocationHandler extends AbstractInvocationHandler {
private Service service;
@Override
protected Object handleInvocation(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(service(), args);
}
private Service service() {
if (service == null) {
service = new DefaultService();
}
return service;
}
}
}

The only thing left is thread safety. We can implement it on our own, but the safest way is to use ConcurrentInitializer from commons-lang:
public class LazyInitWithProxyThreadSafeServiceFactory {
public static Service createService() {
return Reflection.newProxy(Service.class, new ThreadSafeLazyInitInvocationHandler());
}
private static class ThreadSafeLazyInitInvocationHandler extends AbstractInvocationHandler {
private static final LazyInitializer<Service> initializer = new LazyInitializer<Service>() {
@Override
protected Service initialize() {
return new DefaultService();
}
};
@Override
protected Object handleInvocation(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(initializer.get(), args);
}
}
}

I chose LazyInitializer that uses double-checked idiom but there are other implementation that might be more useful depending on the context.

Let's run all the implementations and see how they work (notice when the info about initialization is printed on the console) :
public class App {
private static final Logger logger = LoggerFactory.getLogger(App.class.getName());
public static void main(String[] args) {
invoke(new DefaultService());
logger.debug("----------------------\n");
invoke(new LazyInitService());
logger.debug("----------------------\n");
invoke(LazyInitWithProxyServiceFactory.createService());
logger.debug("----------------------\n");
invoke(LazyInitWithProxyThreadSafeServiceFactory.createService());
}
private static void invoke(Service service) {
logger.debug("Invoking method");
logger.debug("Current time:" + service.getTime().toString());
}
}
view raw App.java hosted with ❤ by GitHub

The whole project can be found on github.