Bom post, mas acho que vale complementar com alguns detalhes.
A implementação sugerida não é thread-safe, ou seja, se várias threads chamarem Singleton.getInstance()
, pode ser que ele crie mais de uma instância. Modifiquei um pouco o seu exemplo para ilustrar melhor:
public final class Singleton {
private static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) { // adicionando uns prints pra saber quando cria uma instância
System.out.println("criando nova instancia: ");
instance = new Singleton();
System.out.println("nova instancia criada: " + instance);
}
return instance;
}
}
E testando com várias threads:
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
public class Teste {
public static void main(String[] args) throws Exception {
List<Callable<Singleton>> tasks = new ArrayList<>();
for (int i = 0; i < 10; i++) {
tasks.add(() -> Singleton.getInstance());
}
ExecutorService executor = Executors.newFixedThreadPool(10);
for (Future<Singleton> result : executor.invokeAll(tasks)) {
System.out.println(result.get());
}
executor.awaitTermination(3, TimeUnit.SECONDS);
executor.shutdown();
}
}
Ou seja, crio 10 threads, e todas elas chamam Singleton.getInstance()
. No final eu percorro os resultados e vejo qual instância foi retornada. O resultado foi:
criando nova instancia:
criando nova instancia:
criando nova instancia:
nova instancia criada: Singleton@47a2a66c
nova instancia criada: Singleton@6f9777db
nova instancia criada: Singleton@1dc022f6
Singleton@47a2a66c
Singleton@47a2a66c
Singleton@47a2a66c
Singleton@6f9777db
Singleton@47a2a66c
Singleton@47a2a66c
Singleton@47a2a66c
Singleton@47a2a66c
Singleton@47a2a66c
Singleton@47a2a66c
No caso, foram criadas 3 instâncias. Claro que isso pode variar a cada execução, já que threads não são determinísticas, mas enfim, se precisar de um singleton em um ambiente multi-thread, aí tem que mudar um pouco a abordagem.
Alternativa thread-safe
Uma forma de resolver é usar a técnica chamada Initialization-on-demand holder, explicada em detalhes aqui. Ficaria assim:
public final class Singleton {
private Singleton() {
System.out.println("criando novo singleton"); // apenas para fins de debug
}
private static class Holder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return Holder.INSTANCE;
}
}
Agora rodando o mesmo código que roda as 10 threads, o resultado é:
criando novo singleton
Singleton@7ba4f24f
Singleton@7ba4f24f
Singleton@7ba4f24f
Singleton@7ba4f24f
Singleton@7ba4f24f
Singleton@7ba4f24f
Singleton@7ba4f24f
Singleton@7ba4f24f
Singleton@7ba4f24f
Singleton@7ba4f24f
Ou seja, apenas uma instância é criada. Basicamente, isso é garantido pela forma como o class loader trabalha. A explicação abaixo foi retirada do link já citado:
- o class loader carrega as classes assim que elas são acessadas (neste caso, o único acesso a
Holder
é dentro do métodogetInstance()
) - quando uma classe é carregada, e antes que qualquer um a use, é garantido que todos os inicializadores estáticos são executados (é aí que o campo
Holder.INSTANCE
é inicializado) - o class loader tem seus próprios mecanismos de sincronização, que garantem que o código acima é thread safe
Indo um pouco mais além, vale lembrar que um singleton é único por JVM. Ou seja, se seu ambiente roda em um cluster, cada nó do cluster terá uma instância. Se quer garantir unicidade entre os clusters, aí precisa de outras técnicas específicas, e varia muito caso a caso (depende do servidor de aplicação, do que exatamente vc está fazendo com o singleton, etc).