Définition du pattern Singleton
Le patron de conception Singleton garantit qu'une classe ne possède qu'une seule instance tout en offrant un point d'accès global à celle-ci. Il s'agit d'un des motifs structurels les plus simples parmi les 23 classiques.
Les composantes essentielles sont :
- Un constructeur privé.
- Une référence statique privée pointant vers l'instance unique.
- Une méthode publique statique retournant cette instance.
Plusieurs variantes d'implémentation existent : initialisation anticipée (eager initialization), initialisation paresseuse (lazy initialization), verrouillage à double vérification, classe enterne statique et enumération.
Initialisation anticipée
Cette approche instancie l'objet dès le chargement de la classe, indépendamment de son utilisation réelle. Simple à coder, elle ne permet pas de retarder la création.
public class InstanceUnique {
private static final InstanceUnique INSTANCE = new InstanceUnique();
private InstanceUnique() {}
public static InstanceUnique recuperer() {
return INSTANCE;
}
}
Initialisation paresseuse
Cette version crée l'instance lors du premier appel. Bien qu'elle permette un chargement différé, sa version basique n'est pas sûre dans un contexte multithread, car plusieurs threads pourraient simultanément créer des instances distinctes.
public class InstanceUnique {
private static InstanceUnique instance;
private InstanceUnique() {}
public static synchronized InstanceUnique obtenir() {
if (instance == null) {
instance = new InstanceUnique();
}
return instance;
}
}
Verrouillage à double vérification
Cette technique combine une vérification hors verrouillage et une vérification après acquisition du verrou. Le mot-clé volatile est crucial pour empêcher les réordonnancements d'instructions, bien qu'il puisse impacter légèrement les performances.
public class InstanceUnique {
private volatile static InstanceUnique instance;
private InstanceUnique() {}
public static InstanceUnique obtenir() {
if (instance == null) {
synchronized (InstanceUnique.class) {
if (instance == null) {
instance = new InstanceUnique();
}
}
}
return instance;
}
}
Classe interne statique
Le chargement de la classe interne statique est différé jusqu'à son premier accès, assurant à la fois le lazy loading et la sécurité thread grâce à la garantie d'initialisation du classloader.
public class InstanceUnique {
private static class Detenteur {
private static final InstanceUnique INSTANCE = new InstanceUnique();
}
private InstanceUnique() {}
public static InstanceUnique obtenir() {
return Detenteur.INSTANCE;
}
}
Enumération
Recommandée par Joshua Bloch dans Effective Java, cette méthode offre une sécurité thread intégrée et une protection contre la désérialisation. Son usage reste cependant moins courant.
public enum InstanceUnique {
INSTANCE;
public void operation() {}
}
Comparaison des approches d'initialisation
L'initialisation anticipée crée l'instance au chargement de la classe, garantissant sa disponibilité immédiate. Elle consomme la mémoire dès le départ mais offre des performances optimales au premier accès.
L'initialisation paresseuse reporte la création au premier appel, ce qui introduit une latence initiale si le processus de construction est lourd, mais économise des ressources si l'instance n'est jamais utilisée.
En pratique, l'initialisation anticipée est généralement préférée en Java pour sa simplicité et sa fiabilité inhérente.
Avantages du pattern Singleton
- Réduction de la consommation mémoire grâce à une instance unique.
- Élimination des surcoûts liés à la création/destruction répétées d'objets.
- Prévention des accès concurrents à des ressources partagées.
- Fourniture d'un point d'accès global.
Cas d'utilisation typiques
Ce pattern convient particulièrement pour :
- Les objets fréquemment instanciés puis détruits.
- Les objets coûteux à créer mais souvent requis.
- Les classes utilitaires avec état.
- Les composants accédant fréquemment à des bases de données ou fichiers.
- Toute situation exigeant une instance unique dans l'application.
Précautions d'implémentation
- Éviter l'utilisation de la réflexion pour obtenir l'instance, car elle peut contourner le mécanisme Singleton.
- Ne pas rompre le lien entre l'instance statique et les références de la classe.
- Toujours considérer la sécurité thread dans les environnements multithreadés.