Padrão Singleton em Java
Implemente o padrão singleton em Java com abordagens eager, lazy e baseadas em enum, com segurança para threads.
O padrão singleton restringe uma classe a uma única instância e fornece um ponto de acesso global a ela. Um facade de logging, um registro de configurações, um cache em processo — são exemplos de coisas que se encaixam bem. Em Java, o padrão possui algumas formas padrão, cada uma com suas próprias trocas entre segurança de threads e carregamento tardio. Há também uma abordagem que evita silenciosamente a maioria dos problemas.
Uma breve advertência antes. "Singleton" é notoriamente fácil de usar em excesso — todo singleton é, na prática, um global, e globais tornam o código mais difícil de testar e raciocinar. A maioria das aplicações Java modernas prefere injeção de dependência: o framework configura uma única instância e a entrega aos componentes que precisam dela, sem que nenhum deles precise chamar Foo.getInstance(). Recorra a um singleton explícito quando DI não está disponível ou é genuinamente exagero.
O que todo singleton precisa
Qualquer implementação de singleton compartilha três elementos:
- Um construtor privado, para que ninguém fora da classe possa chamar
new. - Um campo estático privado que armazena a instância única.
- Um acessor estático público que a retorna.
As variações são principalmente sobre quando a instância é criada e como o acesso permanece seguro para threads.
Inicialização eager
A forma mais simples cria a instância quando a classe é carregada:
public final class Eager {
private static final Eager INSTANCE = new Eager();
private Eager() {}
public static Eager getInstance() { return INSTANCE; }
}A inicialização de classes na JVM é garantidamente segura para threads e executada exatamente uma vez, portanto INSTANCE é definido com segurança sem bloqueios. Use isso quando:
- A construção é barata, ou você sabe que sempre precisará da instância.
- Você não se importa em pagar o custo no momento do carregamento da classe.
Inicialização lazy com double-checked locking
Se a construção é cara e você pode nunca precisar da instância, você pode adiá-la. A versão lazy ingênua não é segura para threads; a correta usa double-checked locking com volatile:
public final class Lazy {
private static volatile Lazy instance;
private Lazy() {}
public static Lazy getInstance() {
Lazy local = instance; // local read avoids re-reading the volatile field
if (local == null) {
synchronized (Lazy.class) {
local = instance;
if (local == null) {
local = new Lazy();
instance = local;
}
}
}
return local;
}
}volatile é essencial — sem ele, outra thread poderia ver o campo definido como uma referência não nula cujo construtor ainda não terminou. Trabalhoso, mas correto.
O holder de inicialização sob demanda
A forma lazy mais limpa usa uma classe aninhada privada. A JVM só carrega o holder quando alguém chama getInstance() pela primeira vez, então o trabalho é adiado — e as garantias de inicialização de classe da JVM cuidam da segurança de threads:
public final class Holder {
private Holder() {}
private static class H {
private static final Holder INSTANCE = new Holder();
}
public static Holder getInstance() { return H.INSTANCE; }
}Sem synchronized, sem volatile, sem double-checked locking — e ainda assim lazy. Esta é a forma lazy a usar na maioria dos códigos.
O singleton enum
O singleton correto mais curto em Java é um enum com uma constante:
public enum Config {
INSTANCE;
public String get(String key) { /* ... */ }
}Config.INSTANCE é o singleton. A recomendação de Joshua Bloch (Effective Java, Item 3) é que esta é a melhor implementação de singleton, porque a JVM garante:
- Exatamente uma instância. Enums são construídos exatamente uma vez por JVM.
- Construção segura para threads. As mesmas garantias de inicialização de classe que o padrão holder.
- Seguro contra reflection. Reflection não pode invocar o construtor de um enum; singletons comuns podem ser derrotados por
Constructor.setAccessible(true). - Seguro contra serialização. Desserializar um singleton normal pode silenciosamente produzir uma segunda instância a menos que você trate
readResolve. Enums são imunes.
A única coisa que não pode fazer é estender outra classe — enums implicitamente estendem java.lang.Enum. Ainda podem implementar interfaces.
Coisas que quebram singletons ingênuos
Fique atento a estes — são a razão pela qual "apenas use um campo estático" nem sempre é suficiente:
- Múltiplos class loaders. Um singleton é um por classloader, não um por JVM. Em contêineres que isolam aplicações com seus próprios loaders, a mesma classe pode ter várias instâncias "únicas".
- Reflection.
setAccessible(true)maisConstructor.newInstance()pode construir uma segunda instância de qualquer singleton que não seja enum. Proteja o construtor comif (INSTANCE != null) throw ...se isso for uma preocupação real. - Serialização. Um singleton
Serializableprecisa deprivate Object readResolve() { return INSTANCE; }para evitar produzir uma segunda cópia a cada desserialização. - Testes. Singletons são notoriamente difíceis de substituir ou reinicializar. Prefira injeção de dependência em código que você espera testar com testes unitários.
Um exemplo prático
O que vem a seguir
Isso encerra a Parte 6 e todo o tour pelo Java orientado a objetos — de classes, herança e polimorfismo até interfaces, enums, records, hierarquias sealed e os métodos que todo objeto herda. A próxima parte amplia o foco para como o código Java é organizado: namespaces, o layout do sistema de arquivos que os espelha e a maquinaria do import que traz tipos de outros lugares para o seu. Continue para Pacotes Java.