Patron de conception Singleton

Le patron Singleton est l'un des patrons de conception les plus simples en Java. Ce type de patron de conception appartient aux patrons de création, et il fournit un moyen optimal de créer des objets.

Ce patron implique une classe unique qui est responsable de la création de son propre objet tout en garantissant qu'un seul objet est créé. Cette classe fournit un moyen d'accéder directement à son objet unique, sans avoir besoin d'instancier un objet de la classe.

Le patron Singleton est un patron de conception de création qui garantit qu'une classe n'a qu'une seule instance et fournit un point d'accès global pour accéder à cette instance.

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.

summary

Patron de conception Singleton

intention

Garantir qu'une classe n'a qu'une seule instance et fournir un point d'accès global pour accéder à cette instance.

Résout principalement

Le problème de la création et de la destruction fréquentes d'instances de classes utilisées globalement.

Quand l'utiliser

Lorsqu'il est nécessaire de contrôler le nombre d'instances et d'économiser les ressources système.

Comment résoudre

Vérifiez si le singleton existe déjà dans le système ; s'il existe, renvoyez cette instance ; s'il n'existe pas, créez une nouvelle instance.

Code clé

Le constructeur est privé.

Exemple d'application

  • Une classe n'a qu'un seul professeur principal.
  • Lorsque Windows effectue des opérations sur des fichiers dans un environnement multi-processus et multithread, pour éviter que plusieurs processus ou threads n'opèrent simultanément sur un même fichier, cela doit être géré via une instance unique.
  • Le gestionnaire de périphériques est conçu selon le modèle singleton. Par exemple, si un ordinateur possède deux imprimantes, cela évite d'imprimer simultanément le même fichier.

Avantages

  • Il n'y a qu'une seule instance en mémoire, ce qui réduit la surcharge mémoire, en particulier lorsque des instances sont fréquemment créées et détruites (comme la gestion du cache des pages d'accueil d'une école de gestion).
  • Éviter l'occupation multiple de ressources (telles que les opérations d'écriture de fichier).

Inconvénients

  • Pas d'interface, ne peut pas être héritée.
  • Cela entre en conflit avec le principe de responsabilité unique ; une classe ne devrait se préoccuper que de sa logique interne, et non de la manière dont elle est instanciée.

Cas d'utilisation

  • Générer des numéros de série uniques.
  • Un compteur dans le développement WEB, évitant que le compteur soit incrémenté dans la base de données à chaque rafraîchissement ; à la place, il est d'abord mis en cache.
  • Créer des objets qui consomment trop de ressources, telles que les E/S et les connexions à la base de données.

Notes

  • Sécurité des threads:getInstance()Des verrous de synchronisation doivent être utilisés dans la méthode.synchronized (Singleton.class)Pour empêcher plusieurs threads d'entrer en même temps, ce qui entraînerait la création multiple de l'instance.
  • Initialisation paresseuse: L'instance est créée au premier appelgetInstance()à la méthode.
  • Sérialisation et désérialisation.: RedéfinirreadResolveMéthode pour garantir qu'aucune nouvelle instance n'est créée pendant la désérialisation.
  • Attaque par réflexion: Ajouter du code de garde dans le constructeur pour empêcher la création de nouvelles instances par réflexion.
  • Problème de chargeur de classes: Soyez conscient des problèmes d'instances multiples qui peuvent être causés par des environnements de chargement de classes complexes.

Structure

Le patron singleton comprend les principaux rôles suivants :

  • Classe singleton : une classe qui contient l'instance singleton, en déclarant généralement le constructeur comme privé.
  • Variable membre statique : une variable membre statique utilisée pour stocker l'instance singleton.
  • Méthode de récupération d'instance : une méthode statique utilisée pour récupérer l'instance singleton.
  • Constructeur privé : empêche le code externe d'instancier directement la classe singleton.
  • Gestion de la sécurité des threads : garantit que la création de l'instance singleton est sûre dans un environnement multithread.

Implémentation

Nous allons créer uneSingleObjectclasse.SingleObjectLa classe possède son constructeur privé et sa propre instance statique.

SingleObjectLa classe fournit une méthode statique pour que le monde extérieur puisse obtenir son instance statique.SingletonPatternDemoUtilisation de la classeSingleObjectclasse pour obtenirSingleObjectobjet.

单例模式的 UML 图

Étape 1

Créer une classe Singleton.

SingleObject.java

public class SingleObject { //Créer un objet de SingleObject private static SingleObject instance = new SingleObject(); //Rendre le constructeur privé afin que la classe ne puisse pas être instanciée private SingleObject(){} //Obtenir l'unique objet disponible public static SingleObject getInstance(){ return instance; } public void showMessage(){ System.out.println("Hello World!"); } }

Étape 2

Obtenir l'unique objet de la classe singleton.

SingletonPatternDemo.java

public class SingletonPatternDemo { public static void main(String[] args) { //Constructeur illégal //Erreur de compilation : le constructeur SingleObject() n'est pas visible //SingleObject object = new SingleObject(); //Obtenir l'unique objet disponible SingleObject object = SingleObject.getInstance(); //Afficher le message object.showMessage(); } }

Étape 3

Exécuter le programme, résultat de sortie :

Hello World!

Plusieurs approches d'implémentation pour le patron de conception singleton

Il existe plusieurs façons d'implémenter le patron de conception singleton, comme indiqué ci-dessous :

1. Mode paresseux, non sécurisé pour les threads

Initialisation paresseuse :Oui

Est-il sécurisé pour le multithreading :non

Difficulté d'implémentation :facile

Description :C'est l'implémentation la plus basique. Le plus gros problème de cette implémentation est qu'elle ne prend pas en charge le multithreading. Comme il n'y a pas de verrou synchronisé, à proprement parler, ce n'est pas un patron singleton.
Cette approche présente un chargement paresseux évident, ne nécessite pas de sécurité des threads et ne peut pas fonctionner correctement en multithreading.

Exemple

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

Les différentes méthodes d'implémentation présentées ci-après prennent toutes en charge le multithreading, mais il existe des différences de performance.

2. Mode paresseux, thread-safe

Initialisation paresseuse :Oui

Est-il sûr en multithread :Oui

Difficulté d'implémentation :facile

Description :Cette approche offre un bon chargement paresseux et peut bien fonctionner en multithread, mais son efficacité est très faible, et la synchronisation n'est pas nécessaire dans 99% des cas.
Avantages : initialisation uniquement au premier appel, ce qui évite le gaspillage de mémoire.
Inconvénients : synchronized doit être ajouté pour garantir le comportement singleton, mais le verrouillage affecte l'efficacité.
Les performances de getInstance() ne sont pas critiques pour l'application (cette méthode n'est pas utilisée fréquemment).

Exemple

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

3. Initialisation hâtive

Initialisation paresseuse :non

Est-il sûr en multithread :Oui

Difficulté d'implémentation :facile

Description :Cette approche est relativement courante, mais elle peut facilement produire des objets inutiles.
Avantages : aucun verrouillage, l'efficacité d'exécution est améliorée.
Inconvénients : initialisé au moment du chargement de la classe, ce qui gaspille de la mémoire.
Il évite les problèmes de synchronisation multithread grâce au mécanisme de chargement de classes. Cependant, l'instance est instanciée lorsque la classe est chargée. Bien qu'il existe de nombreuses raisons qui provoquent le chargement de la classe, dans le modèle singleton, la plupart d'entre elles sont des appels à la méthode getInstance, mais il ne peut pas être confirmé qu'il n'existe pas d'autres façons (ou d'autres méthodes statiques) de provoquer le chargement de la classe. Dans ce cas, initialiser l'instance n'atteint manifestement pas l'effet du chargement paresseux.

Exemple

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

4. Verrouillage à double vérification / double-checked locking (DCL, c'est-à-dire double-checked locking)

Version du JDK :Depuis JDK 1.5

Initialisation paresseuse :Oui

Est-il sûr en multithread :Oui

Difficulté d'implémentation :Plus complexe

Description :Cette approche utilise un mécanisme de double verrouillage, qui est sûr et maintient de hautes performances en multithreading.
Les performances de getInstance() sont critiques pour l'application.

Exemple

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. Registre/Classe interne statique

Initialisation paresseuse :Oui

Est-il sûr en multithread :Oui

Difficulté de mise en œuvre :Moyenne

Description :Cela peut produire le même effet que le verrouillage à double vérification, mais il est plus simple à mettre en œuvre. Pour l'initialisation paresseuse de champs statiques, cette approche doit être utilisée à la place du verrouillage à double vérification. Cette approche ne s'applique qu'aux champs statiques ; le verrouillage à double vérification peut être utilisé lorsque les champs d'instance nécessitent une initialisation paresseuse.
Cette approche utilise également le mécanisme du chargeur de classes pour garantir qu'un seul thread initialise l'instance. Elle diffère de l'approche 3 en ce que : dans l'approche 3, dès que la classe Singleton est chargée, l'instance sera instanciée (sans réaliser le chargement paresseux), alors que dans cette approche, après le chargement de la classe Singleton, l'instance n'est pas nécessairement initialisée. Parce que la classe SingletonHolder n'est pas activement utilisée, c'est seulement lorsque la méthode getInstance est explicitement appelée que la classe SingletonHolder est explicitement chargée, ce qui instancie l'instance. Imaginez que si l'instanciation de l'instance consomme beaucoup de ressources, vous pouvez souhaiter qu'elle soit chargée paresseusement. D'un autre côté, vous pouvez ne pas souhaiter qu'elle soit instanciée lors du chargement de la classe Singleton, car rien ne garantit que la classe Singleton ne sera pas activement utilisée et chargée ailleurs. Dans ce cas, instancier l'instance à ce moment-là est manifestement inapproprié. À ce stade, cette approche est bien plus raisonnable que l'approche 3.

Exemple

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 du JDK :Depuis JDK 1.5

Initialisation paresseuse :non

Est-il sûr en multithread :Oui

Difficulté d'implémentation :facile

Description :Cette approche d’implémentation n’a pas été largement adoptée, mais c’est la meilleure méthode pour implémenter le patron singleton. Elle est plus concise, prend automatiquement en charge le mécanisme de sérialisation et empêche absolument l’instanciation multiple.
Cette approche est préconisée par Josh Bloch, l’auteur d’Effective Java. Non seulement elle évite les problèmes de synchronisation multithread, mais elle prend aussi automatiquement en charge le mécanisme de sérialisation, empêche la désérialisation de recréer de nouveaux objets et empêche absolument l’instanciation multiple. Cependant, comme la fonctionnalité enum n’a été ajoutée qu’après JDK 1.5, cette façon d’écrire paraît inévitablement peu familière, et elle est rarement utilisée dans le travail réel.
Il ne peut pas appeler le constructeur privé via une attaque par réflexion.

Exemple

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

D’après l’expérience :En général, il n’est pas recommandé d’utiliser les 1re et 2e approches paresseuses ; la 3e approche hâtive est recommandée. Ce n’est que lorsqu’un chargement paresseux doit être explicitement mis en œuvre que la 5e approche d’enregistrement doit être utilisée. Si la désérialisation est impliquée dans la création d’objets, vous pouvez essayer la 6e approche enum. S’il existe d’autres exigences particulières, vous pouvez envisager la 4e approche de verrouillage à double vérification.

autres extensions