Conceito e intenção
Singleton garante que uma classe tenha apenas uma instância durante toda a vida da aplicação, fornecendo um ponto de acesso global a ela.
O problema motivador é a existência de recursos que precisam ser únicos por design: um pool de conexões com banco de dados não pode ter duas instâncias criando conexões independentes; um cache em memória precisa ser compartilhado; um registrador de configurações carregado do disco deve ser lido uma só vez. Sem controle, new chamado em vários pontos do sistema criaria instâncias duplicadas, gerando inconsistências.
Singleton resolve isso tornando o construtor privado e expondo a instância através de um método ou campo estático. O padrão tem má reputação — não sem motivo — porque quando mal usado cria estado global e dificulta testes. Em aplicações modernas com contêineres de IoC (Spring, CDI), você raramente precisa implementar o padrão manualmente.
Estrutura
┌─────────────────────────────────────────┐
│ Singleton │
│ - instance: Singleton (static) │
│ - Singleton() (private) │
│ + getInstance(): Singleton (static) │
│ + operacao() │
└─────────────────────────────────────────┘Participantes:
- Singleton — a classe que controla a própria criação e expõe a única instância
Implementação Java — Double-Checked Locking
// Gerenciador de configuração carregado do disco — deve existir uma única vez
public final class ConfigManager {
// volatile garante visibilidade entre threads sem sincronização total
private static volatile ConfigManager instance;
private final Map<String, String> properties;
private ConfigManager() {
// construtor privado — impede new ConfigManager()
this.properties = loadFromDisk(); // operação cara — feita uma vez
}
// Double-checked locking — thread-safe e lazy
public static ConfigManager getInstance() {
if (instance == null) { // 1ª verificação: sem lock (rápido)
synchronized (ConfigManager.class) {
if (instance == null) { // 2ª verificação: dentro do lock
instance = new ConfigManager();
}
}
}
return instance;
}
public String get(String key) {
return properties.getOrDefault(key, "");
}
public String get(String key, String defaultValue) {
return properties.getOrDefault(key, defaultValue);
}
private Map<String, String> loadFromDisk() {
// lê application.properties, env vars, etc.
Map<String, String> props = new LinkedHashMap<>();
// ... carregamento real
return Collections.unmodifiableMap(props);
}
}
// Uso
String dbUrl = ConfigManager.getInstance().get("db.url");
int timeout = Integer.parseInt(ConfigManager.getInstance().get("db.timeout", "5000"));Variação com Initialization-on-Demand Holder
A abordagem mais elegante em Java. Usa a garantia da JVM de que classes internas estáticas são inicializadas apenas uma vez, na primeira vez que são acessadas:
public final class ConfigManager {
private final Map<String, String> properties;
private ConfigManager() {
this.properties = loadFromDisk();
}
// Holder inicializado só quando getInstance() é chamado pela primeira vez
private static final class Holder {
static final ConfigManager INSTANCE = new ConfigManager();
}
public static ConfigManager getInstance() {
return Holder.INSTANCE; // thread-safe sem synchronized
}
public String get(String key) {
return properties.getOrDefault(key, "");
}
}Este idiom (também chamado de Initialization-on-demand Holder) é lazy, thread-safe e não usa volatile nem synchronized explícitos.
Variação com Enum (recomendada para casos simples)
public enum AppConfig {
INSTANCE;
private final Map<String, String> properties;
// Bloco de instância — executado uma vez pela JVM
{
properties = loadFromDisk();
}
public String get(String key) {
return properties.getOrDefault(key, "");
}
public String get(String key, String defaultValue) {
return properties.getOrDefault(key, defaultValue);
}
private Map<String, String> loadFromDisk() {
// carregamento real
return new HashMap<>();
}
}
// Uso — sem getInstance()
String url = AppConfig.INSTANCE.get("db.url");A JVM garante que constantes enum são inicializadas uma única vez, mesmo em ambientes multi-thread. Adicionalmente, enums são protegidos contra desserialização e reflexão criarem instâncias extras — algo que o double-checked locking não garante sem mais código.
Singleton com Spring (forma recomendada em produção)
// Não implemente o padrão manualmente — o container garante singleton
@Configuration
public class InfrastructureConfig {
@Bean // escopo padrão = singleton
public ConnectionPool connectionPool() {
return ConnectionPool.builder()
.url(env.getProperty("db.url"))
.maxSize(20)
.build();
}
@Bean
public CacheManager cacheManager() {
return new CaffeineCacheManager("orders", "products");
}
}
// Injeção normal — Spring cuida da instância única
@Service
public class OrderService {
private final ConnectionPool pool; // singleton injetado
private final CacheManager cache; // singleton injetado
public OrderService(ConnectionPool pool, CacheManager cache) {
this.pool = pool;
this.cache = cache;
}
}Vantagens sobre o Singleton clássico: injeção de dependência explícita, testável (injete mock no teste), sem estado global acessível por qualquer código.
Implementação TypeScript
// Módulo como singleton natural (Node.js/browser com bundler)
// O módulo é avaliado uma vez — o objeto exportado é a instância única
class ConfigManager {
private static instance: ConfigManager | null = null;
private readonly props: Map<string, string>;
private constructor() {
this.props = this.loadConfig();
}
static getInstance(): ConfigManager {
if (!ConfigManager.instance) {
ConfigManager.instance = new ConfigManager();
}
return ConfigManager.instance;
}
get(key: string, defaultValue = ''): string {
return this.props.get(key) ?? defaultValue;
}
private loadConfig(): Map<string, string> {
return new Map(Object.entries(process.env as Record<string, string>));
}
}
// Versão idiomática em TypeScript: módulo singleton
// config.ts
const props = new Map<string, string>(Object.entries(process.env as Record<string, string>));
export const config = {
get: (key: string, defaultValue = '') => props.get(key) ?? defaultValue,
};
// Uso
import { config } from './config';
const dbUrl = config.get('DATABASE_URL');No mundo real
Java Runtime.getRuntime() — retorna a instância única do runtime JVM. É um Singleton clássico com método estático privado.
System.out e System.err — são campos estáticos finais que apontam para uma única instância de PrintStream. Singleton por atribuição estática.
Spring ApplicationContext — dentro de uma aplicação Spring, o contexto é efetivamente um Singleton. Todos os beans com escopo singleton (padrão) existem uma única vez no contexto.
Connection pools (HikariCP, c3p0) — geralmente configurados como singleton via Spring ou injeção manual. Criar múltiplos pools seria desperdício de conexões.
Logback/SLF4J LoggerFactory — LoggerFactory.getLogger(...) retorna instâncias a partir de um registry centralizado. O factory em si é um Singleton.
Node.js require() — módulos são cacheados após a primeira avaliação. Exportar um objeto de um módulo é o padrão singleton mais simples possível em Node.
Quando usar
- Recursos que têm custo alto de inicialização e devem ser compartilhados (pool de conexões, cache)
- Estado de configuração imutável carregado uma vez na inicialização
- Registros de serviço (service registry, plugin registry)
- Gerenciadores de recursos exclusivos de hardware (porta serial, acesso a arquivo de lock)
Quando NÃO usar
- Quando você tem um contêiner de IoC: Spring, CDI, Guice — todos gerenciam singletons para você. Implementar manualmente é redundante e piora a testabilidade.
- Quando o estado muda: Singleton mutável é estado global. Múltiplos threads acessando estado mutável sem sincronização resulta em race conditions.
- Quando dificulta testes:
getInstance()estático não pode ser mockado facilmente. Prefira injeção de dependência — o teste injeta um fake. - Para substituir injeção de dependência: O antipadrão “ServiceLocator” usa Singletons para resolver dependências globalmente — isso obscurece as dependências reais de cada classe.
Problemas clássicos e soluções
Serialização quebra o Singleton: objetos serializados e desserializados criam nova instância. Solução: implementar readResolve() ou usar Enum (imune por design).
// Proteção contra deserialização
protected Object readResolve() {
return getInstance();
}Reflexão pode invocar construtor privado: Constructor.setAccessible(true) burla o modificador privado. Enum é a única forma completamente segura.
Classloaders diferentes: Em ambientes como servidores de aplicação com multiple classloaders, a mesma classe pode existir em loaders distintos — cada um com seu “singleton”. Não é um problema comum em aplicações Spring Boot convencionais.
Combinações com outros padrões
Singleton + Factory Method: A Factory pode ser um Singleton, garantindo um único ponto de criação de objetos. NotificationFactory.getInstance() expõe a factory como instância única.
Singleton + Facade: Facades frequentemente são singletons — existe uma instância do CheckoutFacade gerenciada pelo container que orquestra os subsistemas.
Singleton + Registry: Um Registry de strategies, handlers ou plugins é naturalmente um Singleton — há um registro central compartilhado por toda a aplicação.