Qu'est-ce qu'un singleton ?
Un singleton est l’un des modèles de conception les plus simples pouvant être appliqués à une classe. Les gens disent parfois « cette classe est un singleton », ce qui signifie que cette classe implémente le modèle de conception singleton. Parfois, il est nécessaire d'écrire une classe pour laquelle un seul objet peut être créé. Par exemple, une classe chargée de la journalisation ou de la connexion à une base de données. Le modèle de conception Singleton décrit comment nous pouvons accomplir une telle tâche. Un singleton est un modèle de conception qui fait deux choses :-
Fournit une garantie qu’une classe n’aura qu’une seule instance de la classe.
-
Fournit un point d’accès global à une instance de cette classe.
-
Constructeur privé. Restreint la possibilité de créer des objets de classe en dehors de la classe elle-même.
-
Une méthode statique publique qui renvoie une instance de la classe. Cette méthode est appelée
getInstance
. Il s'agit du point d'accès global à l'instance de classe.
Options de mise en œuvre
Le modèle de conception singleton est utilisé de différentes manières. Chaque option est bonne et mauvaise à sa manière. Ici, comme toujours : il n'y a pas d'idéal, mais il faut s'efforcer de l'atteindre. Mais tout d’abord, définissons ce qui est bon et ce qui est mauvais, et quelles métriques influencent l’évaluation de la mise en œuvre d’un modèle de conception. Commençons par le positif. Voici les critères qui confèrent à la mise en œuvre jutosité et attractivité :-
Initialisation paresseuse : lorsqu'une classe est chargée alors que l'application est en cours d'exécution exactement au moment où elle est nécessaire.
-
Simplicité et transparence du code : la métrique, bien sûr, est subjective, mais importante.
-
Sécurité des threads : fonctionne correctement dans un environnement multithread.
-
Hautes performances dans un environnement multithread : les threads se bloquent peu ou pas du tout lors du partage d'une ressource.
-
Initialisation non paresseuse : lorsqu'une classe est chargée au démarrage de l'application, qu'elle soit nécessaire ou non (un paradoxe, dans le monde informatique il vaut mieux être paresseux)
-
Complexité et mauvaise lisibilité du code. La métrique est également subjective. Nous supposerons que si le sang vient des yeux, la mise en œuvre est médiocre.
-
Manque de sécurité des fils. En d’autres termes, « risque de fil ». Fonctionnement incorrect dans un environnement multithread.
-
Mauvaises performances dans un environnement multi-thread : les threads se bloquent tout le temps ou souvent lors du partage d'une ressource.
Code
Nous sommes maintenant prêts à envisager diverses options de mise en œuvre, en énumérant les avantages et les inconvénients :Solution simple
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return INSTANCE;
}
}
La mise en œuvre la plus simple. Avantages:
-
Simplicité et transparence du code
-
Sécurité du fil
-
Hautes performances dans un environnement multithread
- Pas d’initialisation paresseuse.
Initialisation paresseuse
public class Singleton {
private static Singleton INSTANCE;
private Singleton() {}
public static Singleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
Avantages:
-
Initialisation paresseuse.
-
Pas sûr pour les threads
Accesseur synchronisé
public class Singleton {
private static Singleton INSTANCE;
private Singleton() {
}
public static synchronized Singleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
Avantages:
-
Initialisation paresseuse.
-
Sécurité du fil
-
Mauvaises performances dans un environnement multithread
getInstance
, la méthode est synchronisée et vous ne pouvez la saisir qu'une à la fois. En fait, nous n'avons pas besoin de synchroniser la méthode entière, mais seulement la partie dans laquelle nous initialisons un nouvel objet de classe. Mais nous ne pouvons pas simplement envelopper synchronized
la partie responsable de la création d'un nouvel objet dans un bloc : cela n'assurera pas la sécurité des threads. C'est un peu plus compliqué. La méthode de synchronisation correcte est indiquée ci-dessous :
Verrouillage vérifié deux fois
public class Singleton {
private static Singleton INSTANCE;
private Singleton() {
}
public static Singleton getInstance() {
if (INSTANCE == null) {
synchronized (Singleton.class) {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}
Avantages:
-
Initialisation paresseuse.
-
Sécurité du fil
-
Hautes performances dans un environnement multithread
-
Non pris en charge sur les versions Java inférieures à 1.5 (le mot clé volatile a été corrigé dans la version 1.5)
INSTANCE
doit être soit final
, soit volatile
. La dernière implémentation dont nous discuterons aujourd'hui est Class Holder Singleton
.
Titulaire de la classe Singleton
public class Singleton {
private Singleton() {
}
private static class SingletonHolder {
public static final Singleton HOLDER_INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.HOLDER_INSTANCE;
}
}
Avantages:
-
Initialisation paresseuse.
-
Sécurité du fil.
-
Hautes performances dans un environnement multithread.
-
Pour un fonctionnement correct, il est nécessaire de garantir que l'objet de classe
Singleton
est initialisé sans erreur. Sinon, le premier appel de méthodegetInstance
se terminera par une erreurExceptionInInitializerError
et tous les suivants échouerontNoClassDefFoundError
.
Mise en œuvre | Initialisation paresseuse | Sécurité du fil | Vitesse multithread | Quand utiliser? |
---|---|---|---|---|
Solution simple | - | + | Rapide | Jamais. Ou lorsque l'initialisation paresseuse n'est pas importante. Mais jamais mieux. |
Initialisation paresseuse | + | - | N'est pas applicable | Toujours lorsque le multithreading n'est pas nécessaire |
Accesseur synchronisé | + | + | Lentement | Jamais. Ou lorsque la vitesse de travail avec le multithreading n'a pas d'importance. Mais jamais mieux |
Verrouillage vérifié deux fois | + | + | Rapide | Dans de rares cas où vous devez gérer des exceptions lors de la création d'un singleton. (lorsque le détenteur de classe Singleton n'est pas applicable) |
Titulaire de la classe Singleton | + | + | Rapide | Toujours lorsque le multithreading est nécessaire et qu'il existe une garantie qu'un objet de classe singleton sera créé sans problème. |
Avantages et inconvénients du modèle Singleton
En général, le singleton fait exactement ce qu’on attend de lui :-
Fournit une garantie qu’une classe n’aura qu’une seule instance de la classe.
-
Fournit un point d’accès global à une instance de cette classe.
-
Singleton viole le SRP (Single Responsibility Principe) - la classe Singleton, en plus de ses responsabilités immédiates, contrôle également le nombre de ses copies.
-
La dépendance d'une classe ou d'une méthode régulière sur un singleton n'est pas visible dans le contrat public de la classe.
-
Les variables globales sont mauvaises. Le singleton finit par se transformer en une grande variable globale.
-
La présence d'un singleton réduit la testabilité de l'application en général et des classes qui utilisent le singleton en particulier.
GO TO FULL VERSION