1. Classe singleton de style affamé
}
instance Singleton statique privée = new Singleton ();
Singleton statique privé getInstance(){
instance de retour ;
}
}
Caractéristiques : instanciation de style affamée à l'avance, il n'y a pas de problème de multithread dans le style paresseux, mais peu importe que nous appelions getInstance() ou non, il y aura une instance dans la mémoire
2. Classe singleton de classe interne
}
classe privée SingletonHoledr(){
instance Singleton statique privée = new Singleton ();
}
Singleton statique privé getInstance(){
renvoyer SingletonHoledr.instance ;
}
}
Caractéristiques : Dans la classe interne, le chargement paresseux est implémenté. Ce n'est que lorsque nous appelons getInstance() qu'une instance unique est créée dans la mémoire. Cela résout également le problème du multithread dans le style paresseux. .
3. Classe Singleton paresseuse
}
instance Singleton statique privée ;
public statique Singleton getInstance(){
si(instance == null){
instance de retour = new Singleton();
}autre{
instance de retour ;
}
}
}
Caractéristiques : dans le style paresseux, il y a les threads A et B. Lorsque le thread A passe à la ligne 8, il passe au thread B. Lorsque B s'exécute également à la ligne 8, les instances des deux threads sont vides, il générera donc deux exemples. . La solution est de synchroniser :
La synchronisation est possible mais pas efficace :
}
instance Singleton statique privée ;
Singleton synchronisé statique public getInstance(){
si(instance == null){
instance de retour = new Singleton();
}autre{
instance de retour ;
}
}
}
Il n'y aura aucune erreur en écrivant un programme comme celui-ci, car l'intégralité de getInstance est une "section critique" entière, mais l'efficacité est très mauvaise, car notre objectif est en fait uniquement de verrouiller lors de la première initialisation de l'instance, puis obtenez l'instance lors de l'utilisation, il n'est pas du tout nécessaire de synchroniser les threads.
Des gens intelligents ont donc proposé l’approche suivante :
Vérifiez la méthode d'écriture du verrou :
public static Singleton getSingle(){ //Les objets externes peuvent être obtenus via cette méthode
si (single == null){
synchronisé (Singleton.class) { //Il garantit qu'un seul objet peut accéder à ce bloc synchronisé en même temps
si (single == null){
unique = nouveau Singleton();
}
}
}
return single; //Renvoie l'objet créé
}
}
L'idée est très simple, c'est-à-dire qu'il suffit de synchroniser (synchroniser) la partie du code qui initialise l'instance pour que le code soit à la fois correct et efficace.
Il s’agit du mécanisme dit de « verrouillage à double contrôle » (comme son nom l’indique).
Malheureusement, cette façon d’écrire est erronée sur de nombreuses plates-formes et optimisant les compilateurs.
La raison en est que le comportement de la ligne de code instance = new Singleton() sur différents compilateurs est imprévisible. Un compilateur optimisant peut légalement implémenter instance = new Singleton() comme suit :
1. instance = allouer de la mémoire à la nouvelle entité
2. Appelez le constructeur Singleton pour initialiser les variables membres de l'instance
Imaginez maintenant que les threads A et B appellent getInstance. Le thread A entre en premier et est expulsé du processeur lorsque l'étape 1 est exécutée. Ensuite, le thread B entre, et ce que B voit, c'est que l'instance n'est plus nulle (la mémoire a été allouée), donc il commence à utiliser l'instance en toute confiance, mais c'est faux, car à ce moment, les variables membres de l'instance sont toujours par défaut. valeur, A n’a pas encore eu le temps d’exécuter l’étape 2 pour terminer l’initialisation de l’instance.
Bien entendu, le compilateur peut également l’implémenter comme ceci :
1. temp = allouer de la mémoire
2. Appelez le constructeur de temp
3. instance = temp
Si le compilateur se comporte ainsi, nous semblons n'avoir aucun problème, mais le fait n'est pas si simple, car nous ne pouvons pas savoir comment un certain compilateur fait, car ce problème n'est pas défini dans le modèle de mémoire de Java.
Le verrouillage à double vérification est applicable aux types de base (tels que int). Évidemment, car le type de base n’appelle pas le constructeur.