Singleton with arguments

Posted by BeCodeMonkey on August 18, 2016

Had a very unusual requirement where I needed to share singleton between projects that requires initialization arguments. These arguments are different based on what server it is started. In applications using CDI this was very easy but not all where using CDI and a lot of the projects where not under my control they where simply using the lib. Thread safty was also a concern and I wanted to avoid using synchronized. Here is the solution I came up with.

package com.test;

import java.util.function.Supplier;

public class InitOnce {

	/**
	 * Marked as final to prevent JIT reordering
	 */
	private final Supplier<String> theArgs;

	private InitOnce(Supplier<String> supplier) {
		super();
		this.theArgs = supplier;
	}

	/**
	 * Uses the arguments to do something
	 * 
	 * @return
	 */
	public String doSomething() {
		return "Something : " + theArgs.get();
	}

	/**
	 * Initializes all the things
	 * 
	 * @param someArgs
	 */
	public static synchronized void init(final Supplier<String> someArgs) {
		class InitOnceFactory implements Supplier<InitOnce> {
			private final InitOnce initOnceInstance = new InitOnce(someArgs);

			@Override
			public InitOnce get() {
				return initOnceInstance;
			}
		}

		if (!InitOnceFactory.class.isInstance(instance)) {
			instance = new InitOnceFactory();
		} else {
			throw new IllegalStateException("Already Initialized");
		}
	}

	private static Supplier<InitOnce> instance = new InitOnceHolder();

	/**
	 * Temp Placeholder supplier
	 * 
	 */
	private static final class InitOnceHolder implements Supplier<InitOnce> {
		@Override
		public synchronized InitOnce get() {

			if (InitOnceHolder.class.isInstance(instance))
				throw new IllegalStateException("Not Initialized");

			return instance.get();
		}

	}

	/**
	 * Returns the instance
	 * 
	 * @return
	 */
	public static final InitOnce getInstance() {
		return instance.get();
	}
}

I know having a init function and not using a Factory is a bit messy but it works. Just remember to mark the arguments as final to prevent other threads from reading stale data in the begining. Synchronized blocks is only called on initialized and on the first getInstance() calls until the “instance” vairable is set and visible by all threads. After that is is just like normal Singleton.

To Use

InitOnce.init(() -> "GO COWS!!!")
System.out.println(InitOnce.getInstance().doSomething());

Comments