Its first form is "more correct", that is, without testing in the method getInstance
. The book Effective Java (Effective Java) has discussed this issue deeply for many years.
Let’s see some points below...
Singleton without any competition
The implementation of Singleton Pattern simpler is this way:
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 execute the getInstance
at the same time on the first call to the method.
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 problems, but generates a small delay in each call to the method to manage the competition, in addition to 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 slightly improve the above version, 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 modifier volatile
in the class attribute. It is necessary even with synchronization, because due to the Java memory model, especially before Java 5, errors could still occur caused by a type of cache where others thread could still see the value null
variable, even after assignment by another thread atomic mode.
Beware of multiple command initializations
A very important precaution is not to 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
à instancia
and then passes some object to it. The problem is that as instancia != null
, another thread can call the method getInstancia
and retrieve the object before it receives the dependency. In this case you could have a NullPointerException
.
Singleton preloaded without synchronization
To avoid all these problems above, the simplest solution pointed out is to simply initialize your object Singleton out of method getInstance, exactly as in your first example:
private static PEHandlerService instancia = new PEHandlerService();
public static PEHandlerService getInstancia() {
return instancia;
}
If some kind of initialization is required, it is possible to 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 by demand (Lazy initialization), but as soon as the class is used for the first time (Eager initialization). This can be good or bad, depending on the case.
Singleton concurrent without synchronization
To try to put everything together, that is, avoid synchronization and load the Singleton on-mode Lazy, 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 to declare your class as an Enum of one 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 one can be good or bad for certain situations and some hide certain problems.
However, once you understand the difference between implementations a little bit, it’s not hard to choose one that fits better in your solution.
Although the most common is to see the second form, I’ve done the first way and it works too. In the first form the instance will be created when the class is initialized and in the second form the instance will be created on demand (in the first consumption of getInstance), and this difference in the time of initialization is that it must be confusing some code that we are not seeing in the question (consumer code or parent class code Planilhahandler). Try showing these codes as well as the full exception stack trace.
– Caffé