Implementando o Padrão Singleton em Java: Quatro Abordagens

Inicialização Eager

O padrão eager initialization (inicialização anteicpada) cria a instância no momento do carregamento da classe. Esta abordgaem garante segurança em ambientes multithreaded, porém pode consumir recursos desnecessários caso o objeto nunca seja utilizado.

class ConfiguradorGlobal {
    private static final ConfiguradorGlobal instancia = new ConfiguradorGlobal();
    
    private ConfiguradorGlobal() {
    }
    
    public static ConfiguradorGlobal obterInstancia() {
        return instancia;
    }
}

Quando utilizar:

  • Quando o objeto representa um recurso compartilhado entre múltiplos módulos
  • Como ponto de acesso global (como gerenciadores de configuração ou logging)
  • Quando há alta confiança de que a instância será utilizada
  • Quando o custo de criação do objeto é baixo

Inicialização Lazy (Não Thread-Safe)

A implementação lazy initialization (inicialização preguiçosa) posterga a criação da instância até o primeiro acesso. Embora economize recursos, esta versão não é segura para aplicações multithreaded.

class GerenciadorRecursos {
    private static GerenciadorRecursos referencia;
    
    private GerenciadorRecursos() {
    }
    
    public static GerenciadorRecursos obterInstancia() {
        if (referencia == null) {
            referencia = new GerenciadorRecursos();
        }
        return referencia;
    }
}

Inicialização Lazy (Com Sincronização)

Para resolver o problema de concorrência, podemos adicionar synchronization ao método. Esta solução é thread-safe, porém impacta o desempenho devido ao lock em todas as chamadas.

class ServicoCompartilhado {
    private static ServicoCompartilhado instancia;
    
    private ServicoCompartilhado() {
    }
    
    public static synchronized ServicoCompartilhado obterInstancia() {
        if (instancia == null) {
            instancia = new ServicoCompartilhado();
        }
        return instancia;
    }
}

Double-Checked Locking

A técnica double-checked locking otimiza o desempenho verificando a condição antes e depois do bloco sincronizado, reduzindo a contenção de locks.

class NucleoAplicacao {
    private static volatile NucleoAplicacao contexto;
    
    private NucleoAplicacao() {
    }
    
    public static NucleoAplicacao obterInstancia() {
        if (contexto == null) {
            synchronized (NucleoAplicacao.class) {
                if (contexto == null) {
                    contexto = new NucleoAplicacao();
                }
            }
        }
        return contexto;
    }
}

Static Inner Class (Recomendado)

Esta abordagem utiliza classes internas estáticas para obter lazy loading天然 com segurença thread-safe, sem necessidade de sincronização explícita.

class FabricaServicos {
    private FabricaServicos() {
    }
    
    private static class Holder {
        private static final FabricaServicos instancia = new FabricaServicos();
    }
    
    public static FabricaServicos obterInstancia() {
        return Holder.instancia;
    }
}

Comparativo das Abordagens

Abordagem Thread-Safe Lazy Loading Desempenho
Eager Initialization Sim Não Excelente
Lazy (Não sincronizado) Não Sim Excelente
Lazy (Sincronizado) Sim Sim Regular
Double-Checked Locking Sim Sim Bom
Static Inner Class Sim Sim Excelente

Recomendações de Uso

Para aplicações que necessitam de lazy loading com alta performance em ambiente multithreaded, a abordagem static inner class é amplamente recomendada por sua simplicidade e eficiência. O double-checked locking permanece como excelente opção quando a implementação via classe interna não é viável.

A escolha entre eager e lazy initialization deve considerar o custo de criação do objeto e a probabilidade de utilização efetiva da instância durante o ciclo de vida da aplicação.

Tags: java singleton design-patterns thread-safety lazy-loading

Publicado em 6-12 19:31 por Thomas