Singletons à Java

1. Introduction

Dans cet article rapide, nous discuterons des deux façons les plus populaires d'implémenter des Singletons en Java simple.

2. Singleton basé sur la classe

L'approche la plus populaire consiste à implémenter un Singleton en créant une classe régulière et en s'assurant qu'elle a:

  • Un constructeur privé
  • Un champ statique contenant sa seule instance
  • Une méthode de fabrique statique pour obtenir l'instance

Nous ajouterons également une propriété info, pour une utilisation ultérieure uniquement. Donc, notre implémentation ressemblera à ceci:

public final class ClassSingleton { private static ClassSingleton INSTANCE; private String info = "Initial info class"; private ClassSingleton() { } public static ClassSingleton getInstance() { if(INSTANCE == null) { INSTANCE = new ClassSingleton(); } return INSTANCE; } // getters and setters }

Bien qu'il s'agisse d'une approche courante, il est important de noter qu'elle peut être problématique dans les scénarios de multithreading , ce qui est la principale raison de l'utilisation de Singletons.

En termes simples, cela peut entraîner plus d'une instance, brisant le principe de base du modèle. Bien qu'il existe des solutions de verrouillage à ce problème, notre prochaine approche résout ces problèmes au niveau racine.

3. Enum Singleton

Pour aller de l'avant, ne discutons pas d'une autre approche intéressante - qui consiste à utiliser des énumérations:

public enum EnumSingleton { INSTANCE("Initial class info"); private String info; private EnumSingleton(String info) { this.info = info; } public EnumSingleton getInstance() { return INSTANCE; } // getters and setters }

Cette approche a la sérialisation et la sécurité des threads garanties par l'implémentation enum elle-même, qui garantit en interne que seule l'instance unique est disponible, corrigeant les problèmes signalés dans l'implémentation basée sur les classes.

4. Utilisation

Pour utiliser notre ClassSingleton , nous devons simplement obtenir l'instance de manière statique:

ClassSingleton classSingleton1 = ClassSingleton.getInstance(); System.out.println(classSingleton1.getInfo()); //Initial class info ClassSingleton classSingleton2 = ClassSingleton.getInstance(); classSingleton2.setInfo("New class info"); System.out.println(classSingleton1.getInfo()); //New class info System.out.println(classSingleton2.getInfo()); //New class info

Quant à EnumSingleton , nous pouvons l'utiliser comme n'importe quel autre Java Enum:

EnumSingleton enumSingleton1 = EnumSingleton.INSTANCE.getInstance(); System.out.println(enumSingleton1.getInfo()); //Initial enum info EnumSingleton enumSingleton2 = EnumSingleton.INSTANCE.getInstance(); enumSingleton2.setInfo("New enum info"); System.out.println(enumSingleton1.getInfo()); // New enum info System.out.println(enumSingleton2.getInfo()); // New enum info

5. Pièges courants

Singleton est un modèle de conception d'une simplicité trompeuse, et il y a peu d'erreurs courantes qu'un programmeur pourrait commettre lors de la création d'un singleton.

Nous distinguons deux types de problèmes avec les singletons:

  • existentiel (avons-nous besoin d'un singleton?)
  • mise en œuvre (l'implémentons-nous correctement?)

5.1. Problèmes existentiels

Conceptuellement, un singleton est une sorte de variable globale. En général, nous savons que les variables globales doivent être évitées - surtout si leurs états sont mutables.

Nous ne disons pas que nous ne devrions jamais utiliser de singletons. Cependant, nous disons qu'il pourrait y avoir des moyens plus efficaces d'organiser notre code.

Si l'implémentation d'une méthode dépend d'un objet singleton, pourquoi ne pas le passer en paramètre? Dans ce cas, nous montrons explicitement de quoi dépend la méthode. En conséquence, nous pouvons facilement nous moquer de ces dépendances (si nécessaire) lors des tests.

Par exemple, les singletons sont souvent utilisés pour englober les données de configuration de l'application (c'est-à-dire la connexion au référentiel). S'ils sont utilisés comme objets globaux, il devient difficile de choisir la configuration de l'environnement de test.

Par conséquent, lorsque nous exécutons les tests, la base de données de production est gâtée par les données de test, ce qui n'est guère acceptable.

Si nous avons besoin d'un singleton, nous pourrions envisager la possibilité de déléguer son instanciation à une autre classe - une sorte de fabrique - qui devrait prendre soin de s'assurer qu'il n'y a qu'une seule instance du singleton en jeu.

5.2. Problèmes de mise en œuvre

Même si les singletons semblent assez simples, leurs implémentations peuvent souffrir de divers problèmes. Tous aboutissent au fait que nous pourrions finir par avoir plus d'une seule instance de la classe.

Synchronisation

L'implémentation avec un constructeur privé que nous avons présentée ci-dessus n'est pas thread-safe: elle fonctionne bien dans un environnement monothread, mais dans un environnement multi-thread, nous devrions utiliser la technique de synchronisation pour garantir l'atomicité de l'opération:

public synchronized static ClassSingleton getInstance() { if (INSTANCE == null) { INSTANCE = new ClassSingleton(); } return INSTANCE; }

Notez le mot-clé synchronized dans la déclaration de méthode. Le corps de la méthode comporte plusieurs opérations (comparaison, instanciation et retour).

En l'absence de synchronisation, il est possible que deux threads entrelacent leurs exécutions de telle sorte que l'expression INSTANCE == null soit évaluée à true pour les deux threads et, par conséquent, deux instances de ClassSingleton soient créées.

La synchronisation peut affecter considérablement les performances. Si ce code est souvent appelé, nous devrions l'accélérer en utilisant diverses techniques comme l' initialisation paresseuse ou le verrouillage revérifié (sachez que cela peut ne pas fonctionner comme prévu en raison des optimisations du compilateur). Nous pouvons voir plus de détails dans notre tutoriel «Double-Checked Locking with Singleton».

Instances multiples

Il existe plusieurs autres problèmes avec les singletons liés à la JVM elle-même qui pourraient nous amener à se retrouver avec plusieurs instances d'un singleton. Ces problèmes sont assez subtils et nous en donnerons une brève description:

  1. Un singleton est censé être unique par JVM. Cela peut poser un problème pour les systèmes distribués ou les systèmes dont les composants internes sont basés sur des technologies distribuées.
  2. Chaque chargeur de classe peut charger sa version du singleton.
  3. Un singleton peut être garbage collection une fois que personne ne détient de référence. Ce problème n'entraîne pas la présence de plusieurs instances de singleton à la fois, mais une fois recréée, l'instance peut différer de sa version précédente.

6. Conclusion

Dans ce tutoriel rapide, nous nous sommes concentrés sur la façon d'implémenter le modèle Singleton en utilisant uniquement le noyau Java, et comment nous assurer qu'il est cohérent et comment utiliser ces implémentations.

L'implémentation complète de ces exemples est disponible à l'adresse over sur GitHub.