January 28, 2026 (2mo ago)

Guida al pattern Singleton in Java per codice pulito

Padroneggia il pattern singleton in Java con la nostra guida sulla sicurezza dei thread, i test e alternative moderne come l'iniezione delle dipendenze per codice manutenibile e scalabile.

← Back to blog
Cover Image for Guida al pattern Singleton in Java per codice pulito

Padroneggia il pattern singleton in Java con la nostra guida sulla sicurezza dei thread, i test e alternative moderne come l'iniezione delle dipendenze per codice manutenibile e scalabile.

Guida al pattern Singleton in Java per codice pulito

Sommario: Padroneggia il pattern singleton in Java con la nostra guida sulla sicurezza dei thread, i test e alternative moderne come l'iniezione delle dipendenze per codice manutenibile e scalabile.

Introduzione

Il Pattern Singleton in Java impone che una classe abbia una sola istanza e fornisce un unico punto di accesso globale ad essa. Questo è utile per oggetti come gestori di configurazione, pool di connessioni o servizi centrali di logging dove più istanze causerebbero stato incoerente o spreco di risorse. Capire come implementare un Singleton sicuro e testabile — e quando evitarlo — è essenziale per codice Java pulito e manutenibile.

Comprendere il design pattern Singleton

Una torre di controllo del traffico aereo circondata da aerei colorati in un motivo circolare, che illustra la gestione del traffico aereo.

Un'analogia utile è la torre di controllo del traffico aereo di un aeroporto. Non costruisci una torre separata per ogni aereo; ogni aereo comunica con la stessa torre. La torre è la singola fonte di verità. Questo è il ruolo che un Singleton svolge in un'applicazione.

Il pattern è stato reso popolare nella letteratura classica sui design pattern e nei sistemi enterprise Java1. Le sue responsabilità principali sono semplici:

  • Garantire una singola istanza — tipicamente rendendo il costruttore privato.
  • Fornire un punto di accesso globale — di solito un metodo statico come getInstance().

Caratteristiche chiave

  • Una sola istanza: la classe impedisce la creazione di più di un'istanza.
  • Costruttore privato: impedisce l'instanziazione diretta da altre classi.
  • Punto di accesso globale: un metodo statico restituisce la singola istanza.
  • Ciclo di vita autocontenuto: la classe gestisce la propria istanza.

Punto chiave: il Singleton impone un unico oggetto e un accesso globale controllato affinché diverse parti di un'applicazione parlino con la stessa identica istanza.

Implementare Singletons thread-safe in Java

Un'illustrazione di una cassaforte con un lucchetto, che riceve molte frecce colorate, simbolizzando un archivio sicuro.

Un Singleton pigramente inizializzato ingenuo è semplice ma non thread-safe. Considera questo esempio di base:

public class BasicLazySingleton {
    private static BasicLazySingleton instance;

    private BasicLazySingleton() {}

    public static BasicLazySingleton getInstance() {
        if (instance == null) {
            instance = new BasicLazySingleton();
        }
        return instance;
    }
}

In un ambiente multithread, due thread possono osservare instance == null e creare entrambi una nuova istanza. Una soluzione semplice è sincronizzare getInstance(), ma ciò causa un lock non necessario a ogni chiamata.

Metodo sincronizzato (funziona, ma è costoso)

public class SynchronizedSingleton {
    private static SynchronizedSingleton instance;

    private SynchronizedSingleton() {}

    public static synchronized SynchronizedSingleton getInstance() {
        if (instance == null) {
            instance = new SynchronizedSingleton();
        }
        return instance;
    }
}

Questo risolve la sicurezza dei thread ma penalizza le prestazioni perché la sincronizzazione viene eseguita ad ogni accesso.

Bill Pugh (Initialization-on-demand Holder)

Un approccio più pulito è l'idioma Initialization-on-demand Holder. Offre inizializzazione lazy e sicurezza dei thread senza l'overhead della sincronizzazione perché l'inizializzazione delle classi è thread-safe nella JVM2.

public class BillPughSingleton {
    private BillPughSingleton() {}

    private static class SingletonHolder {
        private static final BillPughSingleton INSTANCE = new BillPughSingleton();
    }

    public static BillPughSingleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

Questo si affida alla JVM per caricare SingletonHolder solo quando viene chiamato getInstance(), e le garanzie sull'inizializzazione delle classi forniscono la sicurezza dei thread.

Enum Singletons (raccomandato)

Joshua Bloch raccomanda di usare un enum a elemento singolo per un Singleton. Questo è conciso e protegge da attacchi via reflection e serializzazione3.

public enum EnumSingleton {
    INSTANCE;

    public void someMethod() {
        // business logic
    }
}

Vantaggi:

  • Codice minimo
  • Sicurezza dei thread fornita dalla JVM
  • Sicurezza nella serializzazione
  • Forte protezione contro la creazione di istanze via reflection

Per la maggior parte dei casi, un enum Singleton è la scelta più robusta e manutenibile.

I costi nascosti del pattern Singleton

Uno schizzo surreale di una stanza con dispositivi elettronici sulle pareti, collegati da fili colorati a numerosi insetti.

Sebbene i Singleton risolvano un problema particolare, introducono costi nascosti: accoppiamento stretto, stato globale e ridotta testabilità.

Quando il codice invoca Singleton.getInstance(), quella dipendenza è nascosta. Il contratto pubblico della classe non rivela che essa fa affidamento su un oggetto globale. Questo porta a:

  • Codice rigido difficile da modificare
  • Test fragili o che richiedono la vera istanza globale
  • Difficoltà a eseguire test in parallelo a causa dello stato condiviso

Problemi di testing

I Singleton rendono difficile il testing unitario isolato. Non puoi facilmente sostituire un mock al suo posto, quindi i test spesso usano l'implementazione reale. Questo può causare test lenti, accoppiamento accidentale a sistemi esterni e pipeline CI fragili.

Una classe che dipende da un Singleton nasconde quella dipendenza dalla sua firma, rendendo il codice più difficile da comprendere e manutenere.

Stato globale e dipendenze nascoste

Un Singleton è essenzialmente una variabile globale. Lo stato globale oscura il flusso di informazioni e crea interdipendenze difficili da districare. Questo complica il debug e rallenta lo sviluppo.

Per approfondire anti-pattern comuni e manutenibilità, consulta la nostra guida sui design pattern nella OOP e sulle strategie di testing.

Alternative moderne ai Singleton

Con l'evoluzione dei sistemi, gli sviluppatori hanno adottato pattern che evitano i problemi del Singleton preservando il controllo sulla creazione degli oggetti.

Dependency Injection

La Dependency Injection (DI) inverte la responsabilità: i client dichiarano le loro dipendenze e un contenitore esterno le fornisce. Questo rende le dipendenze esplicite e facili da sostituire nei test. Framework DI come Spring e Guice gestiscono per te i cicli di vita degli oggetti e il wiring4.

Vantaggi della DI:

  • Decoupling — i componenti dipendono da astrazioni, non da classi concrete
  • Testabilità — puoi iniettare mock o fake
  • Flessibilità — scambia implementazioni tramite configurazione

Questo si allinea con le best practice di inversion of control e produce sistemi più chiari e manutenibili7.

Factory

Il pattern Factory centralizza la logica di creazione. Una factory può restituire la stessa istanza o nuove istanze a seconda delle necessità. Il codice client chiede un oggetto alla factory senza sapere come viene creato, mantenendo il codice modulare e testabile.

Istanza con ambito (Scoped Instances)

A volte serve una singola istanza, ma solo entro uno scope limitato (request, session o application). I framework supportano bean a request-scoped o session-scoped per fornire un bilanciamento tra riuso delle risorse e isolamento.

Singleton nei sistemi distribuiti

Un Singleton locale alla JVM non ti garantisce una singola istanza attraverso più istanze del servizio. I microservizi eseguono più JVM, quindi hai bisogno di soluzioni distribuite per lo stato condiviso, come Redis o un servizio di configurazione centralizzato come Consul o Spring Cloud Config6.

Come refattorizzare i Singleton dal codice legacy

Un uomo smonta un monolite complesso e pesante in molti moduli più piccoli e colorati per un container.

Refattorizzare i Singleton richiede cautela. Il problema principale è la dipendenza diretta sulla chiamata statica getInstance(). Un approccio graduale e metodico riduce il rischio.

Strategia passo-passo:

  1. Introduci un'interfaccia che descriva i metodi pubblici del Singleton e fai in modo che il Singleton la implementi.
  2. Rendi le dipendenze esplicite aggiungendo parametri al costruttore nelle classi che usano il Singleton.
  3. Usa una factory o un contenitore DI (Spring, Guice) per fornire l'implementazione come istanza gestita.
  4. Sostituisci le chiamate a Singleton.getInstance() con dipendenze iniettate tramite costruttore.
  5. Rimuovi il codice specifico del Singleton una volta che tutti i chiamanti ricevono la dipendenza esternamente.

Vantaggi: migliore modularità, testabilità e chiarezza. Refattorizzare i Singleton trasforma un codebase rigido in componenti flessibili e testabili.

Domande frequenti

Il pattern Singleton è un anti-pattern?

Spesso, sì. Il Singleton introduce stato globale e accoppiamento stretto, riducendo la testabilità e aumentando il costo di manutenzione a lungo termine. Usalo con parsimonia e preferisci DI o istanze con scope quando possibile.

Come può essere rotto un Singleton in Java?

Reflection e serializzazione possono creare nuove istanze a meno che non ti difenda esplicitamente. L'uso di un enum per un singleton evita queste insidie3.

Un Singleton è appropriato per i microservizi?

No. Un Singleton è limitato alla JVM, quindi ogni istanza del servizio avrà il proprio Singleton. Per stato condiviso tra servizi, usa sistemi distribuiti come Redis o servizi di configurazione centralizzati6.

Tre sintesi Q&A concise

Q: Quando dovrei usare un Singleton? A: Solo quando una singola istanza rappresenta veramente una risorsa unica e globale all'interno di una singola JVM e non esiste un'alternativa migliore. Preferisci enum Singleton se devi usarlo.

Q: Come creo un Singleton lazy e thread-safe? A: Usa l'idioma Initialization-on-demand Holder (Bill Pugh) o un enum. Entrambi forniscono sicurezza dei thread con overhead minimo; l'enum protegge anche da problemi di serializzazione e reflection23.

Q: Cosa dovrei usare invece dei Singleton per una migliore testabilità? A: Usa Dependency Injection, factory o istanze con scope in modo che le dipendenze siano esplicite e facilmente sostituibili nei test47.


1.
Design Patterns: Elements of Reusable Object-Oriented Software (the “Gang of Four”), Erich Gamma et al., 1994. https://en.wikipedia.org/wiki/Design_Patterns
2.
Java Language Specification, section on initialization of classes and interfaces; class initialization is performed at first active use and is thread-safe. https://docs.oracle.com/javase/specs/
3.
Joshua Bloch, Effective Java — recommends enum singletons for serialization and reflection safety. https://www.pearson.com/en-us/subject-catalog/p/effective-java/P200000006973/9780134685991
4.
Spring Framework documentation on Dependency Injection and bean scopes. https://spring.io/projects/spring-framework
5.
Serialization and reflection pitfalls are discussed in Effective Java and Java serialization documentation. https://docs.oracle.com/javase/8/docs/platform/serialization/spec/serial-arch.html
6.
Microservices patterns and guidance on distributed state; single-JVM singletons don’t provide cross-service singleton semantics. See Microservices.io and related resources. https://microservices.io/
7.
Martin Fowler on Inversion of Control and Dependency Injection principles. https://martinfowler.com/articles/injection.html
← Back to blog
🙋🏻‍♂️

L'AI scrive codice.
Tu lo fai durare.

Nell'era dell'accelerazione AI, il codice pulito non è solo una buona pratica — è la differenza tra sistemi che si scalano e codebase che collassano sotto il loro stesso peso.