W3docs

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) mais Constructor.newInstance() pode construir uma segunda instância de qualquer singleton que não seja enum. Proteja o construtor com if (INSTANCE != null) throw ... se isso for uma preocupação real.
  • Serialização. Um singleton Serializable precisa de private 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

java— editable, runs on the server

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.

Prática

Prática
Por que o singleton enum (`enum X { INSTANCE; ... }`) é geralmente considerado a melhor implementação de singleton em Java?
Por que o singleton enum (`enum X { INSTANCE; ... }`) é geralmente considerado a melhor implementação de singleton em Java?
Was this page helpful?