Maîtrisez le pattern singleton en Java grâce à notre guide sur la sûreté vis-à-vis des threads, les tests et les alternatives modernes comme l'injection de dépendances pour un code maintenable et évolutif.
January 28, 2026 (3mo ago)
Un guide du pattern Singleton en Java pour un code propre
Maîtrisez le pattern singleton en Java grâce à notre guide sur la sûreté vis-à-vis des threads, les tests et les alternatives modernes comme l'injection de dépendances pour un code maintenable et évolutif.
← Back to blog
Un guide du pattern Singleton en Java pour un code propre
Résumé : Maîtrisez le pattern singleton en Java grâce à notre guide sur la sûreté vis-à-vis des threads, les tests et les alternatives modernes comme l'injection de dépendances pour un code maintenable et évolutif.
Introduction
Le pattern Singleton en Java impose qu'une classe n'ait qu'une seule instance et fournit un point d'accès global unique à cette instance. C'est utile pour des objets tels que les gestionnaires de configuration, les pools de connexions ou les services de journalisation centraux où plusieurs instances entraîneraient un état incohérent ou un gaspillage de ressources. Comprendre comment implémenter un Singleton sûr et testable — et quand l'éviter — est essentiel pour du code Java propre et maintenable.
Comprendre le pattern de conception Singleton

Une analogie utile est la tour de contrôle d'un aéroport. On ne construit pas une tour distincte pour chaque avion ; chaque avion communique avec la même tour. La tour est la source unique de vérité. C'est le rôle qu'un Singleton joue dans une application.
Le pattern a été popularisé dans la littérature classique sur les patrons de conception et dans les systèmes Java d'entreprise1. Ses responsabilités principales sont simples :
- Garantir une seule instance — généralement en rendant le constructeur privé.
- Fournir un point d'accès global — habituellement une méthode statique telle que getInstance().
Caractéristiques clés
- Une seule instance : la classe empêche la création de plus d'une instance.
- Constructeur privé : empêche l'instanciation directe par d'autres classes.
- Point d'accès global : une méthode statique renvoie l'unique instance.
- Cycle de vie autonome : la classe gère sa propre instance.
À retenir : Le Singleton impose un objet unique et un accès global contrôlé afin que différentes parties d'une application interagissent avec exactement la même instance.
Implémenter des Singletons sûrs pour les threads en Java

Un Singleton paresseusement initialisé de manière naïve est simple mais pas sûr pour les threads. Considérez cet exemple basique :
public class BasicLazySingleton {
private static BasicLazySingleton instance;
private BasicLazySingleton() {}
public static BasicLazySingleton getInstance() {
if (instance == null) {
instance = new BasicLazySingleton();
}
return instance;
}
}
Dans un environnement multi-thread, deux threads peuvent voir instance == null et créer tous les deux une nouvelle instance. Une correction évidente consiste à synchroniser getInstance(), mais cela entraîne un verrouillage inutile à chaque appel.
Méthode synchronisée (fonctionne, mais coûteux)
public class SynchronizedSingleton {
private static SynchronizedSingleton instance;
private SynchronizedSingleton() {}
public static synchronized SynchronizedSingleton getInstance() {
if (instance == null) {
instance = new SynchronizedSingleton();
}
return instance;
}
}
Cela résout la sûreté pour les threads mais nuit aux performances parce que la synchronisation s'exécute à chaque accès.
Bill Pugh (Initialization-on-demand Holder)
Une approche plus propre est l'idiome Initialization-on-demand Holder. Il offre une initialisation paresseuse et la sûreté pour les threads sans le surcoût de la synchronisation, car l'initialisation des classes est thread-safe dans la JVM2.
public class BillPughSingleton {
private BillPughSingleton() {}
private static class SingletonHolder {
private static final BillPughSingleton INSTANCE = new BillPughSingleton();
}
public static BillPughSingleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
Ceci s'appuie sur la JVM pour charger SingletonHolder uniquement lorsque getInstance() est appelé, et les garanties d'initialisation de classe fournissent la sûreté pour les threads.
Singletons par enum (recommandé)
Joshua Bloch recommande d'utiliser une enum à élément unique pour un Singleton. C'est concis et protège contre les attaques par réflexion et la sérialisation3.
public enum EnumSingleton {
INSTANCE;
public void someMethod() {
// business logic
}
}
Avantages :
- Code minimal
- Sûreté pour les threads fournie par la JVM
- Sécurité lors de la sérialisation
- Forte protection contre la création d'instances via la réflexion
Dans la plupart des cas, un Singleton par enum est le choix le plus robuste et maintenable.
Les coûts cachés du pattern Singleton

Bien que les Singletons résolvent un problème particulier, ils introduisent des coûts cachés : couplage fort, état global et testabilité réduite.
Quand le code appelle Singleton.getInstance(), cette dépendance est cachée. Le contrat public de la classe ne révèle pas qu'elle dépend d'un objet global. Cela conduit à :
- Du code rigide difficile à modifier
- Des tests fragiles ou qui nécessitent l'instance globale réelle
- Des difficultés à exécuter des tests en parallèle à cause de l'état partagé
Problèmes de test
Les Singletons rendent les tests unitaires isolés difficiles. On ne peut pas facilement substituer un mock, si bien que les tests utilisent souvent l'implémentation réelle. Cela peut entraîner des tests lents, un couplage accidentel à des systèmes externes et des pipelines CI fragiles.
Une classe dépendant d'un Singleton masque cette dépendance dans sa signature, ce qui rend le code plus difficile à comprendre et à maintenir.
État global et dépendances cachées
Un Singleton est essentiellement une variable globale. L'état global obscurcit le flux d'information et crée des interdépendances difficiles à démêler. Cela complique le débogage et ralentit le développement.
Pour en savoir plus sur les anti-patterns courants et la maintenabilité, consultez notre guide sur les patrons de conception en POO et les stratégies de test.
Alternatives modernes aux Singletons
À mesure que les systèmes ont évolué, les développeurs ont adopté des patterns qui évitent les pièges du Singleton tout en préservant la création contrôlée d'objets.
Injection de dépendances
L'injection de dépendances (DI) renverse la responsabilité : les clients déclarent leurs dépendances et un conteneur externe les fournit. Cela rend les dépendances explicites et faciles à remplacer dans les tests. Des frameworks DI tels que Spring et Guice gèrent les cycles de vie et le câblage des objets pour vous4.
Avantages de la DI :
- Découplage — les composants dépendent d'abstractions, pas de classes concrètes
- Testabilité — vous pouvez injecter des mocks ou des fakes
- Flexibilité — échangez les implémentations via la configuration
Ceci s'aligne avec les bonnes pratiques d'inversion de contrôle et produit des systèmes plus clairs et plus faciles à maintenir7.
Factories
Le pattern Factory centralise la logique de création. Une factory peut renvoyer la même instance ou de nouvelles instances selon les besoins. Le code client demande un objet à la factory sans savoir comment il est créé, ce qui garde votre code modulaire et testable.
Instances à portée (scoped instances)
Parfois, vous avez besoin d'une unique instance, mais seulement dans une portée limitée (requête, session ou application). Les frameworks supportent des beans scopeés par requête ou par session pour offrir un compromis entre réutilisation des ressources et isolation.
Singletons dans les systèmes distribués
Un Singleton local à la JVM ne vous donne pas une instance unique à travers plusieurs instances de service. Les microservices exécutent plusieurs JVMs, il vous faut donc des solutions distribuées pour l'état partagé, comme Redis ou un service de configuration centralisé comme Consul ou Spring Cloud Config6.
Comment refactorer les Singletons dans du code legacy

Refactorer des Singletons demande de la prudence. Le problème central est la dépendance directe à l'appel statique getInstance(). Une approche progressive et méthodique réduit les risques.
Stratégie pas à pas :
- Introduisez une interface décrivant les méthodes publiques du Singleton, et faites implémenter cette interface par le Singleton.
- Rendez les dépendances explicites en ajoutant des paramètres de constructeur dans les classes qui utilisent le Singleton.
- Utilisez une factory ou un conteneur DI (Spring, Guice) pour fournir l'implémentation en tant qu'instance gérée.
- Remplacez les appels à Singleton.getInstance() par des dépendances injectées via le constructeur.
- Supprimez le code spécifique au Singleton une fois que tous les appelants reçoivent la dépendance de l'extérieur.
Avantages : modularité, testabilité et clarté améliorées. Refactorer les Singletons transforme une base de code rigide en composants flexibles et testables.
Questions fréquemment posées
Le pattern Singleton est-il un anti-pattern ?
Souvent, oui. Le Singleton introduit de l'état global et un couplage fort, ce qui réduit la testabilité et augmente le coût de maintenance à long terme. Utilisez-le avec parcimonie et préférez la DI ou des instances scopeées lorsque c'est possible.
Comment un Singleton peut-il être cassé en Java ?
La réflexion et la sérialisation peuvent créer de nouvelles instances à moins que vous ne vous en protégiez explicitement. L'utilisation d'une enum pour un singleton évite ces écueils3.
Un Singleton est-il approprié pour les microservices ?
Non. Un Singleton est limité à la JVM, donc chaque instance de service possède son propre Singleton. Pour un état partagé entre services, utilisez des systèmes distribués comme Redis ou des services de configuration centralisés6.
Trois résumés Q&R concis
Q : Quand devrais-je utiliser un Singleton ? A : Seulement lorsqu'une unique instance représente véritablement une ressource globale au sein d'une seule JVM et qu'aucune meilleure alternative n'existe. Préférez les Singletons par enum si vous y êtes contraint.
Q : Comment faire un Singleton paresseux et sûr pour les threads ? A : Utilisez l'idiome Initialization-on-demand Holder (Bill Pugh) ou une enum. Les deux fournissent la sûreté pour les threads avec un surcoût minimal ; l'enum protège aussi contre les problèmes de sérialisation et de réflexion23.
Q : Que devrais-je utiliser à la place des Singletons pour une meilleure testabilité ? A : Utilisez l'injection de dépendances, des factories ou des instances scopeées afin que les dépendances soient explicites et facilement remplaçables dans les tests47.
L’IA écrit du code.Vous le faites durer.
À l’ère de l’accélération de l’IA, le code propre n’est pas seulement une bonne pratique — c’est la différence entre les systèmes qui évoluent et les codebases qui s’effondrent sous leur propre poids.