1. Qu'est-ce que le mode singleton
Le modèle Singleton fait référence à l'existence d'un seul cas tout au long de la vie de l'application. Le motif Singleton est un motif de conception largement utilisé. Il présente de nombreux avantages, ce qui peut éviter la création en double d'objets d'instance, réduire les frais généraux du système de création d'instances et enregistrer la mémoire.
Il existe trois exigences pour le mode Singleton:
2. La différence entre le modèle singleton et la classe statique
Tout d'abord, comprenons ce qu'est une classe statique. Une classe statique signifie qu'une classe a des méthodes statiques et des champs statiques. Le constructeur est modifié par privé, il ne peut donc pas être instancié. La classe de mathématiques est une classe statique.
Après avoir su ce qu'est une classe statique, parlons de la différence entre eux:
1) Tout d'abord, le modèle Singleton vous fournira un objet unique à l'échelle mondiale. La classe statique ne vous fournit que de nombreuses méthodes statiques. Ces méthodes n'ont pas besoin d'être créées et peuvent être appelées directement via la classe;
2) Le modèle singleton a une flexibilité plus élevée et les méthodes peuvent être remplacées, car les classes statiques sont toutes des méthodes statiques, de sorte qu'elles ne peuvent pas être remplacées;
3) S'il s'agit d'un objet très lourd, le modèle singleton peut être paresseux à charger, mais les classes statiques ne peuvent pas le faire;
Ensuite, les classes statiques doivent être utilisées et quand devrions-nous utiliser le mode Singleton? Tout d'abord, si vous souhaitez simplement utiliser certaines méthodes d'outils, il est préférable d'utiliser des classes statiques. Les analogies statiques sont plus rapides que les classes singleton, car la liaison statique est effectuée pendant la période de compilation. Si vous souhaitez maintenir des informations d'état ou accéder aux ressources, vous devez utiliser le mode Singleton. On peut également dire que lorsque vous avez besoin de capacités orientées objet (comme l'héritage, le polymorphisme), choisissez des classes Singleton et lorsque vous ne fournissez que des méthodes, choisissez des classes statiques.
3. Comment implémenter le mode singleton
1. mode homme affamé
Le mode soi-disant affamé doit se charger immédiatement. Généralement, des instances ont été générées avant d'appeler la méthode GetInstancef, ce qui signifie qu'elle a été générée lorsque la classe est chargée. L'inconvénient de ce modèle est très évident, c'est qu'il occupe des ressources. Lorsque la classe Singleton est grande, nous voulons réellement l'utiliser puis générer des instances. Par conséquent, cette méthode convient aux classes qui occupent moins de ressources et seront utilisées lors de l'initialisation.
classe singletonhungary {private static singletonhungary singletonhungary = new singletonhungary (); // Définit le constructeur sur privé pour interdire l'instanciation par le biais de nouveaux singletonhungary privés () {} public static singletonhungary getInstance () {return singletonhungary; }}2. Mode paresseux
Le mode paresseux est un chargement paresseux, également appelé chargement paresseux. Créez une instance lorsque le programme doit être utilisé, afin que la mémoire ne soit pas gaspillée. Pour le mode paresseux, voici 5 méthodes d'implémentation. Certaines méthodes de mise en œuvre sont des mises à thread, ce qui signifie que des problèmes de synchronisation des ressources peuvent se produire dans un environnement de concurrence multithread.
Tout d'abord, la première méthode est qu'il n'y a pas de problème dans un seul thread, mais il y aura des problèmes dans le multi-threading.
// Implémentation paresseuse de Singleton Mode 1-Thread Classe dangereuse Singletonlazy1 {private static singletonlazy1 singletonlazy; private singletonlazy1 () {} public static singletonlazy1 getInstance () {if (null == singletonlazy) {try {// simule un peu de préparation avant de créer le thread objet.Sleep (1000); } catch (InterruptedException e) {e.printStackTrace (); } singletonlazy = new singletonlazy1 (); } return singletonlazy; }}Simulons 10 fils asynchrones à tester:
classe publique singletonlazytest {public static void main (String [] args) {thread2 [] threadarr = new Thread2 [10]; for (int i = 0; i <threadarr.length; i ++) {Threadarr [i] = new Thread2 (); Threadarr [i] .start (); }}} // Test Thread Class Thread2 étend Thread {@Override public void run () {System.out.println (singletonlazy1.getInstance (). HashCode ()); }}Résultats en cours:
124191239
124191239
872096466
1603289047
1698032342
1913667618
371739364
124191239
1723650563
367137303
Vous pouvez voir que leurs codes de hash ne sont pas identiques, ce qui signifie que plusieurs objets sont générés dans un environnement multi-thread, qui ne répond pas aux exigences du modèle Singleton.
Alors, comment rendre le fil en sécurité? Dans la deuxième méthode, nous utilisons le mot-clé synchronisé pour synchroniser la méthode GetInstance.
// Singleton Mode Lazy Implementation 2 - Thread Safety // En définissant la méthode de synchronisation, l'efficacité est trop faible, toute la méthode est la classe verrouillée singletonlazy2 {private static singletonlazy2 singletonlazy; private singletonlazy2 () {} public static synchronisé singletonlazy2 getInstance () {try {if (null == singletonlazy) {// simule pour faire une préparation avant de créer le thread de l'objet.Sleep (1000); singletonlazy = new singletonlazy2 (); }} catch (InterruptedException e) {e.printStackTrace (); } return singletonlazy; }}En utilisant la classe de test ci-dessus, les résultats du test:
1210004989
1210004989
1210004989
1210004989
1210004989
1210004989
1210004989
1210004989
1210004989
1210004989
Comme on peut le voir, cette méthode réalise la sécurité du fil. Cependant, l'inconvénient est que l'efficacité est trop faible et qu'elle fonctionne de manière synchrone. Si le thread suivant veut obtenir l'objet, il doit attendre que le fil précédent se remette avant de pouvoir continuer à s'exécuter.
Ensuite, nous ne pouvons pas verrouiller la méthode, mais verrouiller le code à l'intérieur, ce qui peut également réaliser la sécurité des filetages. Mais cette méthode est la même que la méthode de synchronisation, et elle s'exécute également de manière synchrone et a une très faible efficacité.
// Implémentation Singletonlazy 3 - Thread Safety // En définissant des blocs de code synchrones, l'efficacité est trop faible, et le bloc de code entier est une classe verrouillée singletonlazy3 {private static singletonlazy3 singletonlazy; private singletonlazy3 () {} public static singletonlazy3 getInstance () {try {synchronisé (singletonlazy3.class) {if (null == singletonlazy) {// simuler pour faire une préparation avant de créer un objet thread.sleep (1000); singletonlazy = new singletonlazy3 (); }}} catch (InterruptedException e) {// TODO: gère l'exception} return singletonlazy; }}Continuons à optimiser le code. Nous verrouillons uniquement le code qui crée l'objet, mais cela peut-il assurer la sécurité des filetages?
// Implémentation paresseuse du mode Singleton 4 - Thread UsAve // Seul le code qui crée des instances est synchronisé en définissant les blocs de code de synchronisation // mais il y a encore des problèmes de sécurité de thread Singletonlazy4 {private static singletonlazy4 singletonlazy; private singletonlazy4 () {} public static singletonlazy4 getInstance () {try {if (null == singletonlazy) {// code 1 // simule pour faire une certaine préparation avant de créer un thread d'objet.Sleep (1000); synchronisé (singletonlazy4.class) {singletonlazy = new singletonlazy4 (); // Code 2}}} catch (InterruptedException e) {// TODO: Gire Exception} return singletonlazy; }}Jetons un coup d'œil aux résultats en cours d'exécution:
1210004989
1425839054
1723650563
389001266
1356914048
389001266
1560241484
278778395
124191239
367137303
À en juger par les résultats, cette méthode ne peut garantir la sécurité des filetages. Pourquoi? Supposons que deux threads A et B vont dans le «code 1» en même temps, car l'objet est toujours vide pour le moment, donc les deux peuvent entrer la méthode. Enfiler un premier attrape le verrou et crée l'objet. Une fois que le thread B a obtenu le verrou, il ira également au «code 2» lorsqu'il est libéré et un objet est créé. Par conséquent, un singleton ne peut être garanti dans un environnement multithread.
Continuons à optimiser. Puisqu'il y a un problème avec la méthode ci-dessus, nous pouvons simplement porter un jugement nul dans le bloc de code de synchronisation. Cette méthode est notre mécanisme de verrouillage à double vérification DCL.
// Slazy Man in Singleton Mode implémente 5-thread Safety // En définissant le bloc de code de synchronisation, utilisez le mécanisme de verrouillage à double vérification DCL // en utilisant le mécanisme de verrouillage à double vérification résout avec succès la solution d'insécurité et d'efficacité par le thread est également utilisée par la plupart des effets multipliés avec le mode de la lecture de Singleton // la fonction du premier jugement. Lorsque l'objet Singletonlazy5 est créé, lorsque l'objet Singletonlazy5 est obtenu, il n'est pas nécessaire de vérifier le verrou du bloc de code de synchronisation et le code suivant, et de renvoyer directement l'objet Singletonlazy5 // la fonction de l'unité si l'objet: pour résoudre les problèmes de sécurité sous la lecture multithrecaire, c'est-à-dire l'unité de l'objet. classe singletonlazy5 {private statique volatile singletonlazy5 singletonlazy; private singletonlazy5 () {} public static singletonlazy5 getInstance () {try {if (null == singletonlazy) {// simule un peu de préparation avant de créer le thread d'objet.Sleep (1000); synchronisé (singletonlazy5.class) {if (null == singletonlazy) {singletonlazy = new singletonlazy5 (); }}}} catch (InterruptedException e) {} return singletonlazy; }}Résultats en cours:
124191239
124191239
124191239
124191239
124191239
124191239
124191239
124191239
124191239
124191239
Nous pouvons voir que le mécanisme de verrouillage à double vérification DCL résout les problèmes d'efficacité et de sécurité des filetages en mode singleton de chargement paresseux. C'est aussi la méthode que nous utilisons le plus souvent.
mot-clé volatil
Ici, j'ai remarqué que lors de la définition du singletonlazy, le mot-clé volatil est utilisé. Il s'agit d'empêcher les instructions de réorganiser. Pourquoi devons-nous faire cela? Jetons un coup d'œil à un scénario:
Le code va à singletonlazy = new singletonlazy5 (); Il semble que ce soit une phrase, mais ce n'est pas une opération atomique (tous sont exécutés, ou tous ne sont pas exécutés, et la moitié ne peut pas être exécutée). Cette phrase est compilée en 8 instructions d'assemblage, et environ 3 choses sont faites:
1. Allouer de la mémoire à l'instance singletonlazy5.
2. Initialisez le constructeur singletonlazy5
3. Poignez l'objet Singletonlazy à l'espace mémoire alloué (notez que cette instance n'est pas nul).
Étant donné que le compilateur Java permet au processeur d'exécuter hors de l'ordre (ORDRAND) et que l'Ordre du Cache enregistre le rédaction de la mémoire principale dans JMM (Java Memory Medel) avant JDK1.5, l'ordre des deuxième et troisième points ci-dessus ne peut être garanti. C'est-à-dire que l'ordonnance d'exécution peut être du 1-2-3 ou du 1-3-2. S'il s'agit du second, et avant que 3 ne soit exécuté et que 2 ne soit pas exécuté, il est passé au thread 2. À l'heure actuelle, Singletonlazy a déjà exécuté le troisième point du thread One, Singletonlazy est déjà non vide, donc Thread Two prend directement Singletonlazy, puis l'utilise, puis rapporte naturellement une erreur. De plus, ce type d'erreur difficile à suivre et difficile à reproduire peut ne pas être trouvé au cours de la dernière semaine de débogage.
La méthode d'écriture de DCL pour implémenter des singletons est recommandée dans de nombreux livres et manuels techniques (y compris des livres basés sur les versions précédentes de JDK1.4), mais elle n'est en fait pas complètement correcte. En effet, le DCL est réalisable dans certaines langues (comme C), selon que l'ordre des 2 et 3 étapes peut être garanti. Après JDK1.5, le fonctionnaire a remarqué ce problème, donc le JMM a été ajusté et le mot-clé volatil a été concrétisé. Par conséquent, si JDK est une version de 1,5 ou version ultérieure, vous n'avez qu'à ajouter le mot-clé volatil à la définition de Singletonlazy, ce qui peut garantir que Singletonlazy est lu à chaque fois de la mémoire principale, et la réorganisation peut être interdite, et vous pouvez utiliser la méthode d'écriture DCL pour compléter le mode Singleton. Bien sûr, volatile affectera plus ou moins les performances. La chose la plus importante est que nous devons également considérer JDK1.42 et les versions précédentes, donc l'amélioration de la rédaction du modèle singleton se poursuit.
3. Classe intérieure statique
Sur la base des considérations ci-dessus, nous pouvons utiliser des classes intérieures statiques pour implémenter le modèle Singleton, le code est le suivant:
// Implémentez le mode Singleton avec des classes internes statiques-thread-thread class Singletonstaticinner {private singletonstaticinner () {} classe statique privée Singletonner {private static singletonstaticinner singletonstaticinner = new singletonstaticinner (); } public static singletonstaticinner getInstance () {try {thread.sleep (1000); } Catch (InterruptedException e) {// TODO Block de catch généré automatiquement e.printStackTrace (); } return singletoninner.singletonstaticinner; }}On peut voir que nous n'effectuons pas explicitement aucune opération de synchronisation de cette manière, alors comment assure-t-il la sécurité des threads? Comme le mode Hungry Man, c'est une fonctionnalité que JVM garantit que les membres statiques de la classe ne peuvent être chargés qu'une seule fois, de sorte qu'il n'y a qu'un seul objet d'instance du niveau JVM. La question est donc de savoir quelle est la différence entre cette méthode et le modèle Hungry Man? N'est-ce pas le chargement immédiatement? En fait, lorsqu'une classe est chargée, sa classe intérieure ne sera pas chargée en même temps. Une classe est chargée, qui se produit lorsque et seulement si l'un de ses membres statiques (domaines statiques, constructeurs, méthodes statiques, etc.) est appelé.
On peut dire que cette méthode est la solution optimale pour implémenter le modèle Singleton.
4. Blocs de code statique
Voici un modèle de singleton d'implémentation de bloc de code statique. Cette méthode est similaire à la première, et c'est aussi un modèle d'homme affamé.
// Utilisez des blocs de code statique pour implémenter la classe de mode Singleton SingletonStaticBlock {private static singletonstaticblock singletonstaticblock; statique {singletonStaticBlock = new singletonStaticBlock (); } public static singletonstaticblock getInstance () {return singletonstaticblock; }}5. Sérialisation et désérialisation
Pourquoi LZ recommande-t-il la sérialisation et la désérialisation? Étant donné que bien que le mode singleton puisse assurer la sécurité des threads, plusieurs objets seront générés dans le cas de la sérialisation et de la désérialisation. Exécutez la classe de test suivante,
classe publique SingletonStaticInneRerialiseTest {public static void main (String [] args) {try {singletonstaticinneRerialize serialize = singletonstaticinnerserialize.getInstance (); System.out.println (serialize.hashcode ()); // Serialize fileOutputStream fo = new FileOutputStream ("TEM"); ObjectOutputStream oo = new ObjectOutputStream (fo); oo.writeObject (serialize); oo.close (); fo.close (); // désérialiser FileInputStream fi = new FileInputStream ("TEM"); ObjectInputStream oi = new ObjectInputStream (fi); Singletonstaticinnerserialize serialize2 = (singletonstaticinnerserialize) oi.readObject (); oi.close (); fi.close (); System.out.println (serialize2.hashcode ()); } catch (exception e) {e.printStackTrace (); }}} // Utilisez des classes internes anonymes pour implémenter le modèle Singleton. Lors de la rencontre de la sérialisation et de la désérialisation, la même instance n'est pas obtenue.//solve Ce problème consiste à utiliser la méthode ReadResolve pendant la sérialisation, c'est-à-dire supprimer une partie des commentaires. Class SingletonStaticInnerserialize implémente Serializable {/ ** * 28 mars 2018 * / privé statique final SerialVersionUID = 1l; classe statique privée innerclass {private static singletonstaticinnerialize singletonstaticinnerserialize = new singletonstaticinneRerialize (); } public static singletonstaticinnerserialize getInstance () {return innerclass.singletonstaticinneRerialize; } // objet protégé ReadResolve () {// System.out.println ("La méthode ReadResolve a été appelée"); // return innerclass.SingletOnStaticInnerserialize; //}}Vous pouvez voir:
865113938
1078694789
Les résultats montrent que ce sont en effet deux instances d'objets différentes qui violent le modèle singleton. Alors, comment résoudre ce problème? La solution consiste à utiliser la méthode ReadResolve () dans la désérialisation, supprimer le code de commentaire ci-dessus et l'exécuter à nouveau:
865113938
La méthode ReadResolve a été appelée
865113938
La question est de savoir qui est la manière sacrée de la méthode ReadResolve ()? En fait, lorsque le JVM désérialise et "assemble" un nouvel objet de mémoire, il appellera automatiquement cette méthode ReadResolve pour renvoyer l'objet que nous avons spécifié, et les règles Singleton sont garanties. L'émergence de ReadResolve () permet aux programmeurs de contrôler les objets obtenus par désérialisation par eux-mêmes.
Ce qui précède est tout le contenu de cet article. J'espère que cela sera utile à l'apprentissage de tous et j'espère que tout le monde soutiendra davantage Wulin.com.