Your first form is "more correct," that is, without testing in the getInstance
method. The book Effective Java (Effective Java) has already discussed this issue deeply for many years.
Let's see some points below ...
Singleton without competition
Simpler implementation of the singleton pattern is like this:
private static PEHandlerService instancia;
public static PEHandlerService getInstancia() {
if (instancia == null) instancia = new PEHandlerService();
return instancia;
}
As you already know, this version could generate two instances in a somewhat unusual scenario, that is, if two threads executed getInstance
at the same time in the first method call.
Singleton competitor
To solve this, the easiest solution is to synchronize the method:
private static PEHandlerService instancia;
synchronized public static PEHandlerService getInstancia() {
if (instancia == null) instancia = new PEHandlerService();
return instancia;
}
This avoids competition issues, but generates a short delay in each method call to manage the competition, and if there are multiple threads only one can call the method at a time, possibly generating bottlenecks in a highly concurrent system.
Singleton concurrent with minimal synchronization
To improve the above version a little, some authors propose the following construction:
private volatile static PEHandlerService instancia;
public static PEHandlerService getInstancia() {
if (instancia == null) {
synchronized (PEHandlerService.class) {
if (instancia == null) instancia = new PEHandlerService();
}
}
return instancia;
}
This causes synchronization to occur only on startup and not on other calls.
However, note the volatile
modifier in the class attribute. It is necessary even with synchronization, because due to the Java memory model, especially before Java 5, there could still be errors caused by a cache type where other thread you could still see the null
value in the variable, even after the assignment by another thread atomic mode.
Beware of multiple command initializations
A very important care is to not assign the object to the static variable before it is fully initialized. Consider the following code:
private volatile static PEHandlerService instancia;
public static PEHandlerService getInstancia() {
if (instancia == null) {
synchronized (PEHandlerService.class) {
if (instancia == null) {
instancia = new PEHandlerService();
instancia.setAlgumaDependencia(new Dependencia());
}
}
}
return instancia;
}
The above code assigns a new instance of PEHandlerService
to instancia
and then passes some object to it. The problem is that as instancia != null
, another thread can call the getInstancia
method and retrieve the object before it receives the dependency. In this case you could have a NullPointerException
.
Singleton preloaded without synchronization
To avoid all of these problems, the simplest solution is simply to initialize your singleton object outside the getInstance method, just as in your first example:
private static PEHandlerService instancia = new PEHandlerService();
public static PEHandlerService getInstancia() {
return instancia;
}
If some type of boot is required, you can use a static boot block:
private static PEHandlerService instancia;
static {
instancia = new PEHandlerService();
instancia.setDependencia(new Dependencia());
}
public static PEHandlerService getInstancia() {
return instancia;
}
The biggest difference of this approach is that the object will no longer be initialized on demand ( lazy initialization ), but as soon as the class is first used eager initialization ). This can be good or bad depending on the case.
Singleton concurrent without synchronization
To try to merge everything, that is, to prevent synchronization and load singleton in lazy mode, there are some alternatives.
One of them is to use a third class to load the static variable only when it is accessed. Example:
private static class SingletonLoader {
private static PEHandlerService instancia = new PEHandlerService();
}
public static PEHandlerService getInstancia() {
return SingletonLoader.instancia;
}
Alternative: use an Enum
Another alternative to Single is simply declaring your class as an Enum of a single value. Example:
public enum PEHandlerServiceSingleton {
INSTANCE;
//métodos aqui
}
And then you can access it as follows:
PEHandlerServiceSingleton.INSTANCE
Considerations
There are many different ways to use a pattern like Singleton. Each can be good or bad for certain situations and some hide certain problems.
However, once you understand the difference between deployments, it's not hard to choose the one that fits your solution better.