How do you implement a singleton in Java, and what problems does the pattern create?
A singleton is a class with exactly one instance and a global way to reach it. The implementation here relies on two things. The constructor is private, so no outside code can create a second instance. And the instance is held in a static final field that is initialised when the class is first used, so the runtime guarantees the instance is created exactly once and safely published to other threads.
public class Main {
public static void main(String[] args) {
Config a = Config.getInstance();
Config b = Config.getInstance();
System.out.println(a == b);
a.put("env", "prod");
System.out.println(b.get("env"));
}
}
class Config {
private static final Config INSTANCE = new Config();
private final java.util.Map<String, String> values = new java.util.HashMap<>();
private Config() { }
static Config getInstance() { return INSTANCE; }
void put(String key, String value) { values.put(key, value); }
String get(String key) { return values.get(key); }
}The output confirms both properties. The two references are equal, so there is one instance and not two. And the value written through the first reference is visible through the second, because they are the same object, which is exactly the behaviour the pattern promises and the reason it is used.
The problems are the mirror image of the benefit. The state is global, so it is shared by every part of the program, which makes it hard to reason about and impossible to isolate between tests. Two tests that both write to the singleton can interfere with each other, and the interference usually appears only when the suite runs in a particular order.
The constructor being private also makes the class impossible to subclass and hard to mock. And the static initialiser creates a class-loading dependency that is awkward in containers and in tests that need different configurations.