Singletons en Java

Singletons en Java

1. introduction

Dans cet article rapide, nous aborderons les deux méthodes les plus courantes d'implémentation de Singletons en Java standard.

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 que:

  • Un constructeur privé

  • Un champ statique contenant sa seule instance

  • Une méthode d'usine statique pour obtenir l'instance

Nous ajouterons également une propriété d'information, 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 que c’estcan be problematic in multithreading scenarios, qui est la principale raison d’utiliser des Singletons.

En termes simples, il peut en résulter plusieurs instances, rompant ainsi le principe de base du modèle. Bien qu'il existe des solutions verrouillables à ce problème, notre approche suivante résout ces problèmes au niveau racine.

3. Enum Singleton

À l'avenir, n'abordons pas 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 assure la sérialisation et la sécurité des threads, garantie par l'implémentation enum elle-même, ce qui garantit en interne que seule l'instance unique est disponible, corrigeant ainsi les problèmes signalés dans l'implémentation basée sur les classes.

4. Usage

Pour utiliser nosClassSingleton, nous avons simplement besoin d'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 auxEnumSingleton, 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 peut commettre lorsqu'il crée un singleton.

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

  • existentielle (avons-nous besoin d'un singleton?)

  • mise en œuvre (le met-on correctement en œuvre?)

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, en particulier 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 ce dont dépend la méthode. En conséquence, nous pouvons facilement simuler ces dépendances (si nécessaire) lors de l'exécution de 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 en tant qu'objets globaux, il devient difficile de choisir la configuration pour l'environnement de test.

Par conséquent, lorsque nous exécutons les tests, la base de données de production est gâchée par les données de test, ce qui est difficilement acceptable.

Si nous avons besoin d’un singleton, nous pourrions envisager la possibilité de déléguer son instanciation à une autre classe - une sorte d’usine - qui devrait s’assurer qu’il n’ya qu’une instance du singleton en jeu.

5.2. Problèmes de mise en œuvre

Même si les singletons semblent assez simples, leur mise en œuvre peut présenter divers problèmes. Tous résultent dans le fait que nous pourrions finir par avoir plus d'une instance de la classe.

Synchronization L'implémentation avec un constructeur privé que nous avons présenté 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'expressionINSTANCE == null s'évalue àtrue  pour les deux threads et, par conséquent, deux instances deClassSingleton être créé.

Synchronization peut affecter considérablement les performances. Si ce code est souvent appelé, nous devrions l'accélérer en utilisant diverses techniques telles quelazy initialization oudouble-checked locking (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 «https://www.example.com/java-singleton-double-checked-locking[Double-Checked Locking with Singleton]».

Multiple Instances 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 machine virtuelle. Cela peut poser 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 nettoyé une fois que personne ne détient de référence. Ce problème ne conduit pas à la présence de plusieurs instances singleton à la fois, mais une fois recréé, l'instance peut différer de sa version précédente.

6. Conclusion

Dans ce rapide didacticiel, nous nous sommes concentrés sur la manière 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 peut être trouvéeover on GitHub.