Reputation: 889
I have read about many possible ways to create a singleton for the multithreaded environment in Java, like Enums, Double-check locking, etc
.
I found a simple way that is also working fine and I unable to find its drawbacks or failure cases. May anyone explain when it may fail or why we should not choose this approach.
public final class MySingleton {
public final static MySingleton INSTANCE = new MySingleton();
private MySingleton(){}
}
I am testing it with the below code and working fine:
public class MyThread {
public static void main(String[] args) {
for (int i = 0; i < 10000; i++) {
Thread thread = new Thread(() -> {
MySingleton singleton = MySingleton.INSTANCE;
System.out.println(singleton.hashCode() + " " + Thread.currentThread().getName());
});
thread.start();
}
}
}
Every comment is appreciated.
Upvotes: 2
Views: 171
Reputation: 3262
It is perfectly fine and simple. Although you may check the trade offs listed below:
May lead to resource wastage. Because instance of class is created always, whether it is required or not.
CPU time is also wasted for creating the instance if it is not required.
Exception handling is not possible.
Also you have to be sure that MySingleton
class is thread-safe
See Java Singleton Design Pattern Practices with Examples
Upvotes: 1
Reputation: 527
Your singleton is instantiated when the class is loaded, therefore when the main method is started, it is already instantiated and from a multithreading perspective is safe.
But there are some arguments why this might not be the best approach:
And additionally using singletons is not a good design as you hardcode the dependency between the client (that uses this class) and the implementation of this class. So it is hard to replace your implementation with a mock for testing.
Upvotes: 4
Reputation: 2626
Ah, I ran into some problems using exactly this approach a few months ago. Most people here will tell you this is fine, and they are mostly right, as long as you know the constructor won't throw an exception. What I'm about to describe is a common but unpredictable artifact of JVM caching conventions.
I had a singleton that was set up exactly the one you show in your question. Every time I attempted to run the program I got a NoClassDefFoundError
. I had no idea what was going wrong until I found this post on Reddit, aptly titled Using static initialization for singletons is bad!
Just FYI, if you initialize a singleton with static initialization, e.g., private static final MyObject instance = new MyObject(); and there's business logic in your constructor, you can easily run into weird meta-errors, like NoClassDefFoundError. Even worse, it's unpredictable. Certain conditions, such as the OS, can change whether an error occurs or not. Static initialization also includes using a static block, "static{...}" , and using the enum singleton pattern.
The post also links to some Android docs about the cause of the problem.
The solution, which worked very well for me, is called double checked locking.
public class Example {
private static volatile Example SINGLETON = null;
private static final Object LOCK = new Object();
private Example() {
// ...do stuff...
}
public static Example getInstance() {
if (SINGLETON == null) {
synchronized (LOCK) {
if (SINGLETON == null) {
SINGLETON = new Example();
}
}
}
return SINGLETON;
}
}
For the most part, you can probably do it the simple way without problems. But, if your constructor contains enough business logic that you start getting NoClassDefFoundError
, this slightly more complicated approach should fix it.
Upvotes: 0
Reputation: 140309
Yes, this is a fine implementation of a singleton.
The test shows... something; but it doesn't really explain whether it's working or not. What you are trying to show (that only one instance is created) is essentially impossible to show with a test, because it's something that's guaranteed by the language spec.
See JLS 12, in particular JLS 12.4, which describes how classes are initialized.
For each class or interface C, there is a unique initialization lock LC. The mapping from C to LC is left to the discretion of the Java Virtual Machine implementation. The procedure for initializing C is then as follows:
- Synchronize on the initialization lock, LC, for C. This involves waiting until the current thread can acquire LC.
- If the Class object for C indicates that initialization is in progress for C by some other thread, then release LC and block the current thread until informed that the in-progress initialization has completed, at which time repeat this step.
- If the Class object for C indicates that initialization is in progress for C by the current thread, then this must be a recursive request for initialization. Release LC and complete normally.
- If the Class object for C indicates that C has already been initialized, then no further action is required. Release LC and complete normally.
...
So, classes are guaranteed to only be initialized once (if at all), because the initialization is done whilst holding a class-specific lock; the happens-before guarantees of acquiring and releasing that lock mean that the values of final fields are visible; so the INSTANCE
field is guaranteed to be initialized once, and thus there is only one instance of MySingleton
possible.
Note that your implementation is effectively the same as an enum:
public enum MySingleton {
INSTANCE
}
Enums are really just syntactic sugar for classes. If you decompiled an enum class, it would look something like:
public class MySingleton {
public static final MySingleton INSTANCE = new MySingleton(0);
private MySingleton(int ordinal) { ... }
}
Upvotes: 3