You can as a matter of fact implement such a singleton. The problem that was described to you in the comments is the possibility of a class being loaded by multiple ClassLoader
s. Each of these ClassLoader
s can then define a class of identical name which would erroneously assume to be unique.
You can however avoid this by implementing an accessor to your singleton which explicitly relies on checking a specific ClassLoader
for a class of a given name which again contains your singleton. This way, you can avoid that a singleton instance is provided by two different ClassLoader
s and such duplicating the instance you required to be unique throughout the JVM.
For reasons that are explained later, we will split up the Singleton
and the SingletonAccessor
into two different classes. For the following class, we need to later make sure that we always access it by using a specific ClassLoader
:
package pkg;
class Singleton {
static volatile Singleton instance;
}
A convenient ClassLoader
for this matter is the system class loader. The system class loader is aware of all classes on the JVM's class path and has per definition the extension and the bootstrap class loaders as its parents. Those two class loaders do not normally know about any classes that are domain-specific such as our Singleton
class. This safes us from unwanted surprises. Furthermore, we know that it is accessible and known globally throughout a running instance of the JVM.
For now, let us assume that the Singleton
class is on the class path. This way, we can receive the instance by this accessor using reflection:
class SingletonAccessor {
static Object get() {
Class<?> clazz = ClassLoader.getSystemClassLoader()
.findClass("pkg.Singleton");
Field field = clazz.getDeclaredField("instance");
synchronized (clazz) {
Object instance = field.get(null);
if(instance == null) {
instance = clazz.newInstance();
field.set(null, instance);
}
return instance;
}
}
}
By specifying that we explicitly want to load pkg.Singleton
from the system class loader, we make sure that we always receive the same instance despite of which class loader loaded our SingletonAccessor
. In the above example, we additionally make sure that Singleton
is only instantiated once. Alternatively, you could put the instantiation logic into the Singleton
class itself and make the unused instances rot in case other Singleton
classes are ever loaded.
There is however a big drawback. You miss all means of type-safety as you cannot assume that your code is always run from a ClassLoader
which delegates the class loading of Singleton
to the system class loader. This is in particularly true for an application run on an application server which often implements child-first semantics for its class loaders and does not ask the system class loader for known types but first tries to load its own types. Note that a runtime type is characterized by two features:
- Its fully-qualified name
- Its
ClassLoader
For this reason, the SingletonAccessor::get
method needs to return Object
instead of Singleton
.
Another drawback is the fact that the Singleton
type must be found on the class path for this to work. Otherwise, the system class loader does not know about this type. If you can put the Singleton
type onto the class path, you are done here. No problems.
If you cannot make this happen, there is however another way by using for example my code generation library Byte Buddy. Using this library, we can simply define such a type at runtime and inject it into the system class loader:
new ByteBuddy()
.subclass(Object.class)
.name("pkg.Singleton")
.defineField("instance", Object.class, Ownership.STATIC)
.make()
.load(ClassLoader.getSytemClassLoader(),
ClassLoadingStrategy.Default.INJECTION)
You just defined a class pkg.Singleton
for the system class loader and the above strategy is applicable again.
Also, you can avoid the type-safety issues by implementing a wrapper type. You can also automatize this with the help of Byte Buddy:
new ByteBuddy()
.subclass(Singleton.class)
.method(any())
.intercept(new Object() {
@RuntimeType
Object intercept(@Origin Method m,
@AllArguments Object[] args) throws Exception {
Object singleton = SingletonAccessor.get();
return singleton.getClass()
.getDeclaredMethod(m.getName(), m.getParameterTypes())
.invoke(singleton, args);
}
})
.make()
.load(Singleton.class.getClassLoader(),
ClassLoadingStrategy.Default.INJECTION)
.getLoaded()
.newInstance();
You just created a delegator which overrides all methods of the Singleton
class and delegates their invocation to invocations of the JVM-global singleton instance. Note that we need to reload the reflective methods even though they are signature-identical because we cannot rely on the ClassLoader
s of the delegate and the JVM-global classes to be the same.
In practice, you might want to cache the calls to SingletonAccessor.get()
and maybe even the reflective method look-ups (which are rather expensive compared to the reflective method invocations). But this need depends highly on your application domain. If you have trouble with your constructor hierarchy, you could also factor out the method signatures into an interface and implement this interface for both the above accessor and your Singleton
class.