Modèle singleton


Singleton Pattern est l’un des modèles de conception les plus simples de Java. Ce type de modèle de conception est un modèle de création qui offre une manière optimale de créer des objets.

Ce modèle implique une seule classe qui est chargée de créer ses propres objets tout en garantissant qu'un seul objet est créé. Cette classe fournit un moyen d'accéder directement à ses objets uniques, sans avoir besoin d'instancier un objet de la classe.

Remarque :

  • 1 Une classe singleton ne peut avoir qu'une seule instance.

  • 2. La classe singleton doit créer sa propre instance unique.

  • 3. La classe singleton doit fournir cette instance à tous les autres objets.


Introduction

Intention : Garantir qu'une classe n'a qu'une seule instance et fournir un moyen d'y accéder ce Point d'accès mondial.

Solution principale : Une classe utilisée globalement est fréquemment créée et détruite.

Quand utiliser : Lorsque vous souhaitez contrôler le nombre d'instances et économiser les ressources système.

Comment résoudre : Déterminez si le système possède déjà ce singleton, si c'est le cas, renvoyez-le, sinon, créez-le.

Code clé : Le constructeur est privé.

Exemples d'application : 1. Un parti ne peut avoir qu'un seul président. 2. Windows est multi-processus et multithread Lors de l'exploitation d'un fichier, il est inévitable que plusieurs processus ou threads exploitent un fichier en même temps. Par conséquent, tout le traitement du fichier doit être effectué via une instance unique. 3. Certains gestionnaires de périphériques sont souvent conçus en mode singleton. Par exemple, si un ordinateur dispose de deux imprimantes, il faut tenir compte lors de la sortie que deux imprimantes ne peuvent pas imprimer le même fichier.

Avantages : 1. Il n'y a qu'une seule instance dans la mémoire, ce qui réduit la surcharge de mémoire, en particulier lorsque les instances sont fréquemment créées et détruites (comme le cache de la page d'accueil de l'École de gestion). 2. Évitez l'occupation multiple des ressources (telles que les opérations d'écriture de fichiers).

Inconvénients : Il n'y a pas d'interface et ne peut pas être hérité, ce qui entre en conflit avec le principe de responsabilité unique. Une classe ne doit se soucier que de la logique interne et non de la manière de l'instancier à l'extérieur.

Scénarios d'utilisation : 1. Exiger la production d'un numéro de série unique. 2. Le compteur dans le WEB n'a pas besoin d'être ajouté à la base de données à chaque actualisation. Il est d'abord mis en cache avec une seule instance. 3. Un objet créé consomme trop de ressources, telles que les connexions d'E/S et de base de données.

Remarque : Le verrou de synchronisation synchronisé (Singleton.class) doit être utilisé dans la méthode getInstance() pour empêcher plusieurs threads d'entrer en même temps et provoquer l'instanciation de l'instance plusieurs fois.

Implémentation

Nous allons créer une classe SingleObject. La classe SingleObject a son constructeur privé et une instance statique d'elle-même.

La classe

SingleObject fournit une méthode statique permettant au monde extérieur d'obtenir son instance statique. SingletonPatternDemo, notre classe de démonstration utilise la classe SingleObject pour obtenir l'objet SingleObject.

单例模式的 UML 图

Étape 1

Créez une classe Singleton.

SingleObject.java

public class SingleObject {

   //创建 SingleObject 的一个对象
   private static SingleObject instance = new SingleObject();

   //让构造函数为 private,这样该类就不会被实例化
   private SingleObject(){}

   //获取唯一可用的对象
   public static SingleObject getInstance(){
      return instance;
   }

   public void showMessage(){
      System.out.println("Hello World!");
   }
}

Étape 2

Obtenez un objet unique de la classe singleton.

SingletonPatternDemo.java

public class SingletonPatternDemo {
   public static void main(String[] args) {

      //不合法的构造函数
      //编译时错误:构造函数 SingleObject() 是不可见的
      //SingleObject object = new SingleObject();

      //获取唯一可用的对象
      SingleObject object = SingleObject.getInstance();

      //显示消息
      object.showMessage();
   }
}

Étape 3

Vérifiez la sortie.

Hello World!

Plusieurs façons d'implémenter le modèle singleton

Il existe de nombreuses façons d'implémenter le modèle singleton, comme indiqué ci-dessous :

1. Style paresseux, thread non sécurisé

.

Si Initialisation paresseuse : Oui

Si elle est multi-thread sécurisée : Non

Difficulté de mise en œuvre : Facile

Description : Cette méthode est la méthode d'implémentation la plus basique. Le plus gros problème avec cette implémentation est qu'elle ne prend pas en charge le multi-threading. Puisqu’il n’y a pas de verrouillage synchronisé, il n’est pas considéré comme un mode singleton au sens strict.
Cette méthode de chargement paresseux ne nécessite évidemment pas de sécurité des threads et ne fonctionnera pas correctement avec plusieurs threads.

Exemple de code :

public class Singleton {  
    private static Singleton instance;  
    private Singleton (){}  
  
    public static Singleton getInstance() {  
    if (instance == null) {  
        instance = new Singleton();  
    }  
    return instance;  
    }  
}


Les méthodes d'implémentation présentées ensuite prennent toutes en charge le multi-threading, mais en termes de performances, il y a une différence.

2. Style paresseux, thread-safe

Si l'initialisation paresseuse : Oui

Si elle est multi-thread-safe : Oui

Difficulté de mise en œuvre : Facile

Description : Cette méthode a un bon chargement paresseux et peut bien fonctionner en multi-threads Cependant, l'efficacité est très faible et la synchronisation n'est pas nécessaire dans 99 % des cas.
Avantages : Il n'est initialisé qu'au premier appel pour éviter le gaspillage de mémoire.
Inconvénient : la synchronisation doit être verrouillée pour garantir le singleton, mais le verrouillage affectera l'efficacité.
Les performances de getInstance() ne sont pas critiques pour l'application (la méthode est utilisée moins fréquemment).

Exemple de code :

public class Singleton {  
    private static Singleton instance;  
    private Singleton (){}  
    public static synchronized Singleton getInstance() {  
    if (instance == null) {  
        instance = new Singleton();  
    }  
    return instance;  
    }  
}


3. Style chinois affamé

Si initialisation paresseuse : Non

Est-il multi-thread sécurisé :Oui

Difficulté de mise en œuvre :Facile

Description :Cette méthode est plus couramment utilisée, mais elle est facile à générer des objets poubelles.
Avantages : Pas de verrouillage, l'efficacité d'exécution sera améliorée.
Inconvénient : La classe est initialisée lors du chargement, ce qui gaspille de la mémoire.
Il est basé sur le mécanisme classloder pour éviter les problèmes de synchronisation multi-thread. Cependant, l'instance est instanciée lorsque la classe est chargée. Bien qu'il existe de nombreuses raisons pour le chargement de la classe, la plupart d'entre elles appellent la méthode getInstance en mode singleton, mais il n'est pas non plus certain qu'il existe d'autres moyens (ou d'autres méthodes statiques) provoquant le chargement de classe. À l'heure actuelle, l'initialisation de l'instance n'obtient évidemment pas l'effet de chargement paresseux.

Exemple de code :

public class Singleton {  
    private static Singleton instance = new Singleton();  
    private Singleton (){}  
    public static Singleton getInstance() {  
    return instance;  
    }  
}


4. Verrouillage à double contrôle/verrouillage à double contrôle (DCL, verrouillage à double contrôle)

Version JDK : JDK1.5 et supérieur

Initialisation paresseuse : Oui

S'il est multi-thread safe : Oui

Difficulté de mise en œuvre : Plus complexe

Description : Cette méthode est utilisée Mécanisme de double verrouillage, sûr et capable de maintenir des performances élevées dans des situations multithread.
Les performances de getInstance() sont essentielles à l'application.

Exemple de code :

public class Singleton {  
    private volatile static Singleton singleton;  
    private Singleton (){}  
    public static Singleton getSingleton() {  
    if (singleton == null) {  
        synchronized (Singleton.class) {  
        if (singleton == null) {  
            singleton = new Singleton();  
        }  
        }  
    }  
    return singleton;  
    }  
}


5. Classe interne enregistrée/statique

Si initialisation paresseuse. : Oui

S'il est multi-thread sécurisé : Oui

Difficulté de mise en œuvre : Général

Description :Cette méthode peut obtenir le même effet que la méthode de verrouillage à double vérification, mais la mise en œuvre est plus simple. Utilisez l'initialisation paresseuse pour les champs statiques. Cette méthode doit être utilisée à la place de la méthode de verrouillage par double vérification. Cette méthode s'applique uniquement aux domaines statiques. La méthode de verrouillage par double vérification peut être utilisée lorsque le domaine d'instance nécessite une initialisation retardée.
Cette méthode utilise également le mécanisme classloder pour garantir qu'il n'y a qu'un seul thread lors de l'initialisation de l'instance. Elle est différente de la troisième méthode : dans la troisième méthode, tant que la classe Singleton est chargée, l'instance sera instanciée. (non Pour obtenir un effet de chargement paresseux), de cette façon, la classe Singleton est chargée et l'instance n'est pas nécessairement initialisée. Étant donné que la classe SingletonHolder n'est pas activement utilisée, la classe SingletonHolder ne sera explicitement chargée qu'en appelant la méthode getInstance pour instancier l'instance. Imaginez si l'instanciation d'une instance consomme des ressources, vous souhaitez donc qu'elle soit chargée paresseusement. D'un autre côté, vous ne souhaitez pas l'instancier lorsque la classe Singleton est chargée, car il n'y a aucune garantie que la classe Singleton puisse être utilisée activement. à d'autres endroits et donc en cours de chargement, alors il est évidemment inapproprié d'instancier l'instance à ce moment. À l’heure actuelle, cette méthode semble plus raisonnable que la troisième méthode.

Exemple de code :

public class Singleton {  
    private static class SingletonHolder {  
    private static final Singleton INSTANCE = new Singleton();  
    }  
    private Singleton (){}  
    public static final Singleton getInstance() {  
    return SingletonHolder.INSTANCE;  
    }  
}


6. Énumération

Version JDK : Démarrage de JDK1.5

S'il doit être initialisé par paresseux : Non

S'il doit être multi-thread safe : Oui

Difficulté de mise en œuvre :Facile

Description : Cette implémentation n'a pas été largement adoptée, mais c'est la meilleure façon d'implémenter le modèle singleton. Il est plus concis, prend automatiquement en charge le mécanisme de sérialisation et empêche absolument les instanciations multiples.
Cette méthode est préconisée par Josh Bloch, auteur d'Effective Java. Elle évite non seulement les problèmes de synchronisation multi-thread, mais prend également automatiquement en charge le mécanisme de sérialisation pour empêcher la désérialisation de recréer de nouveaux objets et empêche absolument les instanciations multiples. Cependant, comme la fonctionnalité d'énumération n'a été ajoutée qu'après JDK 1.5, écrire de cette manière donne aux gens un sentiment de méconnaissance et est rarement utilisée dans le travail réel.
Les constructeurs privés ne peuvent pas être appelés via une attaque par réflexion.

Exemple de code :

public enum Singleton {  
    INSTANCE;  
    public void whateverMethod() {  
    }  
}


Expérience : Généralement, il n'est pas recommandé d'utiliser les 1ère et 2ème méthodes paresseuses, et il est recommandé d'utiliser la 3ème méthode affamée. La cinquième méthode d'enregistrement ne sera utilisée que lorsque l'effet de chargement paresseux est explicitement implémenté. S'il s'agit de désérialiser et de créer des objets, vous pouvez essayer la sixième méthode d'énumération. Si vous avez d’autres besoins particuliers, vous pouvez envisager d’utiliser la quatrième méthode de verrouillage à double vérification.