Tout le monde connaît le modèle Singleton, et ils savent tous pourquoi il est paresseux, affamé, etc. Mais avez-vous une compréhension approfondie du modèle Singleton? Aujourd'hui, je vous emmènerai voir les singletons dans mes yeux, ce qui peut être différent de votre compréhension.
Voici un petit exemple simple:
// simple classe publique paresseuse singleton {// instance singleton variable private static singleton instance = null; // Méthode de construction privée pour s'assurer que les classes externes ne peuvent pas être instanciées via le constructeur privé singleton () {} // obtenez singleton instance public static singleton getInstance () {if (instance == null) {instance = new Singleton (); } System.out.println ("Je suis un simple singleton paresseux!"); retour d'instance; }}Il est facile de voir que le code ci-dessus est dangereux dans le cas du multi-threading. Lorsque deux threads entrent If (instance == null), les deux threads jugent que l'instance est vide, puis deux instances seront obtenues. Ce n'est pas le singleton que nous voulons.
Ensuite, nous utilisons le verrouillage pour atteindre l'exclusion mutuelle pour assurer la mise en œuvre des singletons.
// Méthode synchrone Classe publique paresseuse Singleton {// Instance singleton variable private static singleton instance = null; // Méthode de construction privée pour s'assurer que les classes externes ne peuvent pas être instanciées via le constructeur privé singleton () {} // Obtenez une instance de singleton public statique synchronisé singleton getInstance () {if (instance == null) {instance = new singleton (); } System.out.println ("Je suis une méthode synchrone Lazy Singleton!"); retour d'instance; }}L'ajout de synchronisé garantit la sécurité des filetages, mais est-ce la meilleure façon? De toute évidence, ce n'est pas le cas, car de cette façon, chaque fois que nous appelons la méthode getInstance (), nous serons verrouillés, et nous n'avons besoin de le verrouiller que lorsque nous appelons getInstance () la première fois. Cela affecte évidemment les performances de notre programme. Nous continuons à trouver de meilleures façons.
Après l'analyse, il a été constaté que ce n'est qu'en garantissant que l'instance = new Singleton () est une exclusion mutuelle, la sécurité du fil peut être assurée, de sorte que la version suivante est disponible:
// Double Lock Lazy Public Class Singleton {// Singleton Instance variable private static singleton instance = null; // Méthode de construction privée pour s'assurer que les classes externes ne peuvent pas être instanciées via le constructeur privé singleton () {} // Obtenez singleton instance public static singleton getInstance () {if (instance == null) {synchronisé (singleton.class) {if (instance == null) {instance = new singleton (); }}} System.out.println ("Je suis un singleton paresseux à double serrure!"); retour d'instance; }} Cette fois, il semble que non seulement résout le problème de sécurité du fil, mais ne fait pas ajouter le verrouillage à chaque fois que vous appelez getInstance (), ce qui entraîne une dégradation des performances. Cela ressemble à une solution parfaite, est-ce en fait de cette façon?
Malheureusement, le fait n'est pas aussi parfait que nous le pensions. Il existe un mécanisme appelé "Écritures hors service" dans le modèle de mémoire de la plate-forme Java. C'est ce mécanisme qui provoque la défaillance de la méthode de verrouillage à double vérification. La clé de ce problème réside dans la ligne 5 sur le code ci-dessus: instance = new Singleton (); Cette ligne fait en fait deux choses: 1. Appelez le constructeur et créez une instance. 2. Attribuez cette instance à l'instance de variable d'instance. Mais le problème est que ces deux étapes de JVM ne garantissent pas la commande. C'est-à-dire. Il se peut que l'instance ait été définie sur non vide avant d'appeler le constructeur. Analyons-le ensemble:
Supposons qu'il y ait deux fils A et B
1. Le thread a entre dans la méthode getInstance ().
2. Parce que l'instance est vide à l'heure actuelle, le fil A entre dans le bloc synchronisé.
3. Thread a exécute instance = new Singleton (); Définit l'instance de variable d'instance sur non vide. (Notez que c'est avant d'appeler le constructeur.)
4. Le fil a les sorties et le fil B entre.
5. Le thread B vérifie si l'instance est vide, et elle n'est pas vide pour le moment (dans la troisième étape, il est défini sur le fil A). Le thread B renvoie une référence à l'instance. (Le problème se pose. Pour le moment, la référence à l'instance n'est pas une instance singleton car le constructeur n'est pas appelé.)
6. Le thread B sort et le fil A entre.
7. Thread A continue d'appeler la méthode du constructeur, complète l'initialisation de l'instance et les retours.
N'y a-t-il pas un bon moyen? Il doit y avoir un bon moyen, continuons à explorer!
// Résoudre le problème de l'écriture non ordonnée de classe publique paresseuse singleton {// d'instance singleton variable private static singleton instance = null; // Méthode de construction privée pour s'assurer que les classes externes ne peuvent pas être instanciées via le constructeur privé singleton () {} // obtenez singleton instance public static singleton getInstance () {if (instance == null) {synchronisé (singleton.class) {// 1 singleton temp = instance; // 2 if (temp == null) {synchronisé (singleton.class) {// 3 temp = new singleton (); // 4} instance = temp; // 5}}} System.out.println ("Je résolve l'écriture non ordonnée de singletons paresseux!"); retour d'instance; }} 1. Le thread a entre dans la méthode getInstance ().
2. Parce que l'instance est vide, le thread A entre dans le premier bloc synchronisé en position // 1.
3. Thread a exécute le code en position // 2 et affecte l'instance à la température de variable locale. L'instance est vide, donc la température est également vide.
4. Parce que la température est vide, le fil A entre dans le deuxième bloc synchronisé en position // 3. (Je pensais que cette serrure était un peu redondante)
5. Le thread a exécute le code en position // 4, définissant la température sur non vide, mais le constructeur n'a pas encore été appelé! ("Écriture non commande")
6. Si le thread a des blocs, le thread B entre dans la méthode getInstance ().
7. Parce que l'instance est vide, Thread B essaie d'entrer le premier bloc synchronisé. Mais parce que le fil A est déjà à l'intérieur. Il est donc impossible d'entrer. Blocs de filetage B.
8. Le thread A est activé et continue d'exécuter le code en position // 4. Appelez le constructeur. Générer une instance.
9. Attribuez la référence d'instance de Temp à l'instance. Sortez deux blocs synchronisés. Renvoie l'instance.
10. Le thread B est activé et entre dans le premier bloc synchronisé.
11. Thread B exécute le code en position // 2 et affecte l'instance d'instance à la variable locale temporaire.
12. Le thread B détermine que la température variable locale n'est pas vide, saute donc le bloc IF. Renvoie l'instance d'instance.
Jusqu'à présent, nous avons résolu le problème ci-dessus, mais nous avons soudainement constaté que pour résoudre le problème de sécurité du fil, on a l'impression qu'il y a beaucoup de fils enroulés sur le corps ... c'est désordonné, nous devons donc le rationaliser:
// la classe publique affamée Singleton {// La variable singleton, statique, est initialisée une fois lorsque la classe est chargée pour assurer une instance de singleton statique privée de filetage STATIQUE = new Singleton (); // Méthode de construction privée pour s'assurer que les classes externes ne peuvent pas être instanciées via le constructeur. Singleton privé () {} // Obtenez l'instance d'objet Singleton public Singleton Singleton GetInstance () {System.out.println ("Je suis un singleton affamé!"); retour d'instance; }}Quand j'ai vu le code ci-dessus, j'ai instantanément senti que le monde était silencieux. Cependant, cette méthode adopte la méthode affamée de style homme, qui est d'obtenir des objets Singleton pré-déplaçant. Un inconvénient de cela est: si le singleton de la construction est important et qu'il n'est pas utilisé une fois la construction terminée, elle entraînera un gaspillage de ressources.
Y a-t-il un moyen parfait? Continuez à regarder:
// La classe intérieure implémente la classe publique paresseuse Singleton {classe statique privée singletonholder {// variable singleton instance statique privée singleton = new singleton (); } // Méthode de construction privée pour s'assurer que les classes externes ne peuvent pas être instanciées via le constructeur. private singleton () {} // Obtenez l'instance d'objet singleton public static singleton getInstance () {System.out.println ("Je suis une classe interne singleton!"); retour singletonholder.instance; }}Paresseux (éviter les déchets de ressources ci-dessus), la file et le code simple. Parce que le mécanisme Java stipule que le singletonholder de classe interne ne sera chargé que lorsque la méthode getInstance () est appelée pour la première fois (implémentation paresseuse), et que son processus de chargement est en file d'attente (implémentation de filetage). L'instance est instanciée lorsque la classe interne est chargée.
Parlons brièvement de l'écriture non ordonnée mentionnée ci-dessus. C'est la caractéristique de JVM. Par exemple, déclarant deux variables, String A; String b; JVM peut charger un premier ou b. De même, instance = new singleton (); Peut définir l'instance sur non vide avant d'appeler le constructeur de Singleton. C'est une question pour beaucoup de gens, disant qu'un objet de Singleton n'a pas été instancié, alors comment l'instance est-elle devenue non vide? Quelle est sa valeur maintenant? Si vous voulez comprendre ce problème, vous devez comprendre comment l'instance de phrase = new Singleton (); est exécuté. Voici un pseudo-code pour vous l'expliquer:
mem = allocate (); // allocation de la mémoire aux objets singleton. instance = mem; // Notez que l'instance n'est pas vide maintenant, mais n'a pas encore été initialisée. ctorsingleton (instance); // Appelez le constructeur de Singleton et pass d'instance.
On peut voir que lorsqu'un thread exécute l'instance = mem;, l'instance n'est pas vide. Si un autre thread entre dans le programme et juge l'instance comme non vide, il sautera pour retourner l'instance; Et à l'heure actuelle, le constructeur de Singleton n'a pas appelé l'instance, et la valeur actuelle est l'objet mémoire renvoyé par allocate ();. Ainsi, le deuxième thread ne reçoit pas un objet Singleton, mais un objet de mémoire.
Ce qui précède est mes petites pensées et ma compréhension du modèle Singleton. J'accueille chaleureusement tous les grands dieux pour venir guider et critiquer.