Quelles sont les différentes manières de casser un modèle singleton en Java?

Quelles sont les différentes manières de casser un modèle de singleton en Java? Je connais un moyen, à savoir si nous ne synchronisons pas la méthode en singleton, nous pouvons créer plus qu’une instance de la classe. Donc, la synchronisation est appliquée. Y at-il un moyen de casser singleton java class.

public class Singleton { private static Singleton singleInstance; private Singleton() { } public static Singleton getSingleInstance() { if (singleInstance == null) { synchronized (Singleton.class) { if (singleInstance == null) { singleInstance = new Singleton(); } } } return singleInstance; } } 

En commençant par votre code donné, le “locking à double vérification” peut être bloqué dans certains environnements. Lorsqu’il est exécuté sur un système utilisant le JIT Symantec, cela ne fonctionne pas. En particulier, le JIT Symantec comstack

 singletons[i].reference = new Singleton(); 

(notez que le JIT Symantec utilise un système d’allocation d’objects basé sur des descripteurs).

 0206106A mov eax,0F97E78h 0206106F call 01F6B210 ; allocate space for ; Singleton, return result in eax 02061074 mov dword ptr [ebp],eax ; EBP is &singletons[i].reference ; store the unconstructed object here. 02061077 mov ecx,dword ptr [eax] ; dereference the handle to ; get the raw pointer 02061079 mov dword ptr [ecx],100h ; Next 4 lines are 0206107F mov dword ptr [ecx+4],200h ; Singleton's inlined constructor 02061086 mov dword ptr [ecx+8],400h 0206108D mov dword ptr [ecx+0Ch],0F84030h 

Comme vous pouvez le constater, l’affectation à singletons [i] .reference est effectuée avant l’appel du constructeur de Singleton. Ceci est complètement légal dans le modèle de mémoire Java existant, et également en C et C ++ (car aucun d’entre eux n’a de modèle de mémoire).

http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html

Mis à part cela

  1. Cela peut casser si la classe est Serializable
  2. Il peut casser si son ‘Clonable`
  3. Vous pouvez casser par Reflection (je crois)
  4. il peut casser ff plusieurs classloaders sont chargés la classe

* Comment résolvez-vous les règles brisées?

  1. Il est beaucoup plus sûr de faire une initialisation rapide
  2. Pour empêcher la désérialisation afin de créer un nouvel object, vous pouvez remplacer la méthode readResolve() dans votre classe et readResolve() exception.
  3. Pour empêcher le clonage, vous pouvez remplacer clone() et lancer CloneNotSupported exception CloneNotSupported
  4. Pour échapper à un instant de reflection, nous pouvons append un contrôle dans le constructeur et lever une exception.

Exemple

 public class Singleton { private static final Singleton INSTANCE = new Singleton(); private Singleton() { // Check if we already have an instance if (INSTANCE != null) { throw new IllegalStateException("Singleton" + " instance already created."); } } public static final Singleton getInstance() { return INSTANCE; } private Object readResolve() throws ObjectStreamException { return INSTANCE; } private Object writeReplace() throws ObjectStreamException { return INSTANCE; } public Object clone() throws CloneNotSupportedException { // return INSTANCE throw new CloneNotSupportedException(); } } 

Après tout, je suggérerais d’utiliser Enum comme moyen le plus sûr pour Singleton (depuis java5, le meilleur moyen de le faire est d’utiliser un enum).

 public static enum SingletonFactory { INSTANCE; public static SingletonFactory getInstance() { return INSTANCE; } } 

En fait, une version sûre sans synchronisation est la version avec une classe de détenteurs nestede :

 public final class Singleton{ public static final Singleton getInstance(){ // no need for synchronization since the classloader guarantees to initialize // Holder.INSTANCE exactly once before handing out a reference to it return Holder.INSTANCE; } private Singleton(); private static class Holder{ private static final Singleton INSTANCE = new Singleton(); } } 

Les autres versions sûres sont:

  • Initialisation désirée

     public final class Singleton{ public static final Singleton getInstance(){ return INSTANCE; } private Singleton(); private static final Singleton INSTANCE = new Singleton(); } 
  • Enum Singleton

     public enum Singleton{ INSTANCE; } 

Toutes ces versions ont des avantages et des inconvénients, mais aucune d’elles n’a besoin d’une synchronisation explicite, car elles reposent toutes sur le ClassLoader et sa sécurité intégrée dans Thread.

Comme d’autres l’ont écrit, vous pouvez briser certains de ces schémas en procédant à la désérialisation. Lisez Effective Java de Joshua Bloch (Articles 74 à 78) sur la prévention de telles attaques (le modèle enum singleton est sécurisé contre les attaques de ce type).

Un moyen est la sérialisation. Si vous n’implémentez pas readResolve, la lecture d’un singleton avec ObjectInputStream.readObject () renverra une nouvelle instance de ce singleton.

Autant que je sache, il y a deux façons de le briser

  • Utilisation de la reflection

  • Lorsqu’il y a des chargeurs de classe personnalisés, plus d’un singletons (c.-à-d. Un chargeur de classe parent) .all doit être chargé par un chargeur de classe parent commun.

 import java.io.Serializable; public class Singleton implements Serializable,Cloneable{ private static final long serialVersionUID = 1L; private static Singleton singleton=null; private Singleton(){ } public static Singleton getInstance(){ if(singleton==null){ singleton=new Singleton(); } return singleton; } @Override public Object clone() throws CloneNotSupportedException{ return super.clone(); } } 

** Test Singleton **

 import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /*** * * Ways to break Singleton */ public class Main { private static ObjectInputStream inputStream; public static void main(Ssortingng[] args) throws Exception { Singleton orginalSingletonObject = Singleton.getInstance(); /*** * Singleton is broken by using Reflection */ breakSingletonByReflection(orginalSingletonObject); /*** * By Serialization/De-Serialization break Singleton We need * Serialization interface in a class nedds to be serialized like * Singleton.java */ breakSingletonByserialization(orginalSingletonObject); /*** * By Cloning break Singleton * We need to implement Cloneable interface */ breakSingletonByCloning(orginalSingletonObject); /*** * Break Singleton By thread * This scenario is related to multi-threading environment * */ breakSingletonByThreading(orginalSingletonObject); } private static void breakSingletonByThreading(Singleton orginalSingletonObject) { ExecutorService executorService=Executors.newFixedThreadPool(2); /** * Run this code snippet after commenting the other code for better understanding * Run it repeatly to create a condition when 2 threads enter the method getInstance() of Singleton class at a same time * When 2 threads enter the getInstance method at same time they will get the singleton object as null (private static Singleton singleton in Singleton.java) * Then they will create two different objects ( have different hashcode) in this case singleton pattern will break. */ executorService.submit(Main::useSingleton); // JAVA 8 syntax it will get the singleton instance executorService.submit(Main::useSingleton); executorService.shutdown(); } public static void useSingleton(){ Singleton singleton=Singleton.getInstance(); printSingletonData("By Threading", singleton); } private static void breakSingletonByCloning(Singleton orginalSingletonObject) throws CloneNotSupportedException { Singleton clonedSingletonObject=(Singleton) orginalSingletonObject.clone(); printSingletonData("By Cloning", orginalSingletonObject, clonedSingletonObject); } private static void breakSingletonByReflection(Singleton orginalsingleton) throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException { Class singletonClass = Class.forName("SingletonTest.Singleton"); @SuppressWarnings("unchecked") Constructor constructor = (Constructor) singletonClass .getDeclaredConstructor(); constructor.setAccessible(true); Singleton s = constructor.newInstance(); printSingletonData("By Reflection", orginalsingleton, s); } private static void breakSingletonByserialization(Singleton orginalsingleton) throws FileNotFoundException, IOException, ClassNotFoundException { /** * Serialization */ ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("E:\\Singleton.ser")); outputStream.writeObject(orginalsingleton); outputStream.close(); /** * DeSerialization */ inputStream = new ObjectInputStream(new FileInputStream("E:\\Singleton.ser")); Singleton deserializeObject = (Singleton) inputStream.readObject(); deserializeObject.hashCode(); printSingletonData("By Serialization", orginalsingleton, deserializeObject); } public static void printSingletonData(Ssortingng operationName, Singleton orginalsingleton, Singleton reflectionSigletonObject) { System.out.println("------------------------------------------"); System.out.println("New Operation"); System.out.println(operationName); System.out.println("orginal Hashcode=" + orginalsingleton.hashCode()); System.out.println("New Object hashcode=" + reflectionSigletonObject.hashCode()); Boolean value = orginalsingleton.hashCode() != reflectionSigletonObject.hashCode(); System.out.println("These Object have different hascode. They are two different object Right = " + value); System.out.println("As these are different Object this means Singleton Pattern is broken"); } private static void printSingletonData(Ssortingng operationName,Singleton singleton) { System.out.println("------------------------------------------"); System.out.println("New Operation"); System.out.println(operationName); System.out.println("Object hashcode=" + singleton.hashCode()); } } 

Classe cls = Singleton.class;

  Constructor constructor = cls.getDeclaredConstructor(); constructor.setAccessible(true); Singleton singleton = (Singleton) constructor.newInstance(); 

Le multithreading est le plus gros problème avec les singletons. Vous pouvez éviter cela en synchronisant ou en utilisant une initialisation rapide.

Une autre façon serait d’utiliser singleton où vous n’êtes pas censé le faire. En utilisant le modèle singleton, vous pouvez l’appliquer de manière à gêner votre programme lors des développements ultérieurs. (Par exemple, créer un “joueur” unique dans un jeu, parce que vous supposez qu’il s’agit d’un jeu à joueur unique. Prochaine étape du développement “append une fonctionnalité de coopération”).

Le motif singleton a ses avantages, mais ne l’utilisez pas sans y prêter attention.

Une remarque d’abord: il serait préférable dans ce cas, principalement pour des getSingleInstance() { } de lisibilité, de rendre l’ensemble de getSingleInstance() { } synchronized :

 public synchronized static Singleton getSingleInstance() { if (singleInstance == null) { singleInstance = new Singleton(); } return singleInstance; } 

À part cela, je ne pense pas qu’il soit facile de casser le code. Bien sûr, si vous ajoutez des appels récursifs, il est possible de les interrompre, comme ceci:

  • Appelez le constructeur d’une autre classe à l’intérieur du constructeur de ce Singleton .
  • Dans le constructeur de cette classe, essayez d’obtenir cette instance Singleton .

Mais c’est la seule chose à laquelle je peux penser, et vous ne pouvez pas vous protéger de cela dans la classe Singleton .

L’approche synchronisée fonctionnera, mais ralentira également chaque access au singleton pour protéger quelque chose qui ne se produit que lors du premier access.

Le moyen le plus simple et le plus sûr consiste simplement à effectuer une initialisation rapide, ce qui est toujours sûr, car Java garantit que toutes les variables membres sont définies avant de permettre à quiconque d’y accéder.

 public class Singleton { private static Singleton singleInstance = new Singleton(); private Singleton() { } public static Singleton getSingleInstance() { return singleInstance; } } 

Votre approche actuelle est en fait interrompue même avec votre boucle synchronisée, car le locking à double contrôle est interrompu. Vous devez marquer la variable singleton comme volatile si vous voulez l’utiliser pour un double contrôle du locking, faute de quoi les threads ont toujours des moyens d’accéder à un object incomplètement initialisé. Voir http://en.wikipedia.org/wiki/Double-checked_locking#Usage_in_Java pour plus de détails.

Supposons que vous avez implémenté la classe Singleton en tant que “SingletonPattern” comme ci-dessous. package com.example.pattern;

 public class SingletonPattern { private static SingletonPattern pattern; public static SingletonPattern getInstance() { if (pattern == null) { synchronized (SingletonPattern.class) { if (pattern == null) { pattern = new SingletonPattern(); } } } return pattern; } } 

Maintenant, vous pouvez modifier le comportement Singleton de cette classe en utilisant l’approche suivante.

 Class c = Class.forName("com.example.pattern.SingletonPattern"); System.out.println((SingltonPattern) c.newInstance()); 

nous pouvons casser le modèle de conception singletone à l’aide du clonage d’object et pour éviter que le clonage améliore les performances, nous pouvons utiliser la technique de reflection