Maison >Java >javaDidacticiel >Comment résoudre les problèmes de mode java singleton et de sécurité des threads
Le mode cas unique signifie garantir qu'une classe n'a qu'une seule instance unique et fournit un point d'accès global.
Catégorie : Style paresseux, style homme affamé
Pourquoi avons-nous besoin du mode singleton ?
Dans certains cas particuliers, il est nécessaire qu'une classe ne puisse être utilisée que pour générer un objet unique. Par exemple : il y a de nombreuses imprimantes dans la salle des imprimantes, mais son système de gestion d'impression ne dispose que d'un seul objet de contrôle des tâches d'impression, qui gère la file d'attente d'impression et attribue des tâches d'impression à chaque imprimante. Le modèle singleton a été créé pour répondre à ces besoins.
Idée d'implémentation :
Afin d'empêcher le client d'utiliser le constructeur pour créer plusieurs objets, déclarez le constructeur comme type privé. Mais cela rendra cette classe indisponible, donc une méthode statique capable d'obtenir une instance doit être fournie, généralement appelée méthode getInstance, qui renvoie une instance. Cette méthode doit être statique, car les méthodes statiques sont appelées en fonction du nom de la classe, sinon elles ne peuvent pas être utilisées.
Diagramme de classe : style chinois paresseux
Diagramme de classe : style chinois affamé
Regardons d'abord un exemple simple :
Classe de test singleton : Chien
//懒汉式 public class Dog { private static Dog dog; private String name; private int age; //私有的构造器 private Dog() {} public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } //静态工厂方法 public static Dog getInstance() { if (dog == null) { dog = new Dog(); } return dog; } @Override public String toString() { return "Dog [name=" + name + ", age=" + age + "]"; } }
Classe de test singleton : Cat
//饿汉式 public class Cat { private static Cat cat = new Cat(); private String name; private int age; //私有构造器 private Cat() {} public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } //静态工厂方法 public static Cat getInstance() { return cat; } @Override public String toString() { return "Cat [name=" + name + ", age=" + age + "]"; } }
Classe de test
import java.util.HashSet; import java.util.Set; public class Client { public static void main(String[] args) { //单线程模式测试 Dog dog1 = Dog.getInstance(); Dog dog2 = Dog.getInstance(); System.out.println("dog1 == dog2: "+(dog1 == dog2)); Cat cat1 = Cat.getInstance(); Cat cat2 = Cat.getInstance(); System.out.println("cat1 == cat2: "+(cat1 == cat2)); } }
Résultat d'exécution
créer la différence
le style paresseux est appelé pour la première fois Les objets Singleton sont créés lorsque la méthode statique getInstance() est utilisée.
Le style chinois affamé consiste à créer un objet singleton lorsque la classe est chargée, c'est-à-dire à instancier la classe singleton lors de la déclaration d'un objet singleton statique.
Thread safety
Le style paresseux est thread-unsafe, tandis que le style affamé est thread-safe (sera testé ci-dessous).
Occupation des ressources
Le style paresseux n'est créé que lorsqu'il est utilisé, tandis que le style affamé est créé lorsque la classe est chargée. Par conséquent, le style de l'homme paresseux n'est pas aussi rapide que celui de l'homme affamé, mais le style de l'homme affamé consomme plus de ressources s'il n'est pas utilisé tout le temps, il occupera beaucoup de ressources.
Classe multi-thread
import java.util.HashSet; import java.util.Set; public class DogThread extends Thread{ private Dog dog; private Set<Dog> set; public DogThread() { set = new HashSet<>(); } //这个方法是为了测试添加的。 public int getCount() { return set.size(); } @Override public void run() { dog = Dog.getInstance(); set.add(dog); } }
Classe de test multi-thread
import java.util.HashSet; import java.util.Set; public class Client { public static void main(String[] args) { //单线程模式测试 Dog dog1 = Dog.getInstance(); Dog dog2 = Dog.getInstance(); System.out.println("dog1 == dog2: "+(dog1 == dog2)); Cat cat1 = Cat.getInstance(); Cat cat2 = Cat.getInstance(); System.out.println("cat1 == cat2: "+(cat1 == cat2)); //多线程模式测试 DogThread dogThread = new DogThread(); Thread thread = null; for (int i = 0; i < 10; i++) { thread = new Thread(dogThread); thread.start(); } try { Thread.sleep(2000); //主线程等待子线程完成! } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("dog's number: "+dogThread.getCount()); } }
Résultats d'exécution
Remarque : les résultats du multi-threading sont difficiles à prédire, les threads sont impliqué ici En compétition, les résultats peuvent être les mêmes plusieurs fois (les mêmes plusieurs fois ne signifient pas que c'est absolument correct), mais tant que vous testez plusieurs fois, vous pouvez voir des résultats différents.
Explication
Ici j'utilise une petite technique de collecte, en utilisant les caractéristiques de la collection Set, pour stocker les objets chien générés à chaque fois dans la collection Set, et enfin j'appelle simplement la taille( ) méthode de collecte C'est tout. On peut voir que deux objets chien sont générés, ce qui signifie qu'une erreur s'est produite, qui est une erreur de programmation. Il est également important de comprendre que les erreurs ne peuvent pas se produire en mode multithread, de sorte que les objets dog générés sont plus petits que le nombre de threads.
Étant donné que le singleton de style Hungry est thread-safe, il ne sera pas testé ici. Si vous êtes intéressé, vous pouvez le tester.
La solution à la sécurité des threads singleton paresseux : synchronisation
Remarque : il existe de nombreuses méthodes de synchronisation, et vous pouvez également utiliser Lock pour le traitement. La synchronisation est une méthode, pas spécifiquement le mot-clé synchronzied. Ceux qui sont intéressés peuvent en savoir plus. .
Et la méthode de synchronisation est généralement plus lente et doit être pesée en termes de performances.
//静态同步工厂方法 public synchronized static Dog getInstance() { if (dog == null) { dog = new Dog(); } return dog; }
Voici un mode multi-instance, c'est-à-dire que le nombre d'objets est un nombre fixe. Vous pouvez voir la promotion du modèle singleton. Bien sûr, il existe de nombreuses façons de le mettre en œuvre. Voici ma méthode.
Classe en mode multi-instance
//固定数目实例模式 public class MultiInstance { //实例数量,这里为四个 private final static int INSTANCE_COUNT = 4; private static int COUNT = 0; private static MultiInstance[] instance = new MultiInstance[4]; private MultiInstance() {}; public static MultiInstance getInstance() { //注意数组的下标只能为 COUNT - 1 if (MultiInstance.COUNT <= MultiInstance.INSTANCE_COUNT - 1) { instance[MultiInstance.COUNT] = new MultiInstance(); MultiInstance.COUNT++; } //返回实例前,执行了 COUNT++ 操作,所以 应该返回上一个实例 return MultiInstance.instance[MultiInstance.COUNT-1]; } }
Classe de test
import java.util.HashSet; import java.util.Set; public class Test { public static void main(String[] args) { System.out.println("------------------------"); testMultiInstance(); } //测试多实例模式(单例的扩展,固定数目实例) public static void testMultiInstance() { Set<MultiInstance> instanceSet = new HashSet<>(); MultiInstance instance = null; for (int i = 0; i < 10; i++) { instance = MultiInstance.getInstance(); instanceSet.add(instance); } System.out.println("8个实例中,不同的实例有:"+instanceSet.size()); } }
Résultats d'exécution
Remarque : si elle est utilisée dans un environnement multithread, la sécurité des threads doit également être prise en compte. Si vous êtes intéressé, vous pouvez le mettre en œuvre vous-même.
Le modèle singleton est-il nécessairement sûr ?
Pas nécessairement, il existe de nombreuses façons de briser le schéma singleton !
这里举例看一看(我只能举我知道的哈!其他的感兴趣,可以去探究一下!)
使用反射:这种办法是非常有用的,通过反射即使是私有的属性和方法也可以访问了,因此反射破坏了类的封装性,所以使用反射还是要多多小心。但是反射也有许多其他的用途,这是一项非常有趣的技术(我也只是会一点点)。
使用反射破坏单例模式测试类
这里使用的还是前面的 Dog 实体类。注意我这里的**包名:**com。
所有的类都是在 com包 下面的。
import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; public class Client { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { Class<?> clazz = Class.forName("com.Dog"); Constructor<?> con = clazz.getDeclaredConstructor(); //设置可访问权限 con.setAccessible(true); Dog dog1 = (Dog) con.newInstance(); Dog dog2 = (Dog) con.newInstance(); System.out.println(dog1 == dog2); } }
说明:反射的功能是很强大的,从这里既可以看出来,正是有了反射,才使得Java 语言具有了更多的特色,这也是Java的强大之处。
使用对象序列化破坏单例模式
测试实体类:Dog(增加一个对象序列化接口实现)
import java.io.Serializable; //懒汉式 public class Dog implements Serializable{ private static final long serialVersionUID = 1L; private static Dog dog; private String name; private int age; //私有的构造器 private Dog() {} public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } //静态工厂方法 public synchronized static Dog getInstance() { if (dog == null) { dog = new Dog(); } return dog; } @Override public String toString() { return "Dog [name=" + name + ", age=" + age + "]"; } }
对象序列化测试类
import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; public class Client { public static void main(String[] args) throws IOException, ClassNotFoundException { Dog dog1 = Dog.getInstance(); dog1.setName("小黑"); dog1.setAge(2); System.out.println(dog1.toString()); ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(dog1); ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis); Dog dog2 = (Dog) ois.readObject(); System.out.println(dog2.toString()); System.out.println("dog1 == dog2: "+(dog1 == dog2)); } }
运行结果
说明
这里可以看出来通过对象序列化(这里也可以说是对象的深拷贝或深克隆),
同样也可以实现类的实例的不唯一性。这同样也算是破坏了类的封装性。对象序列化和反序列化的过程中,对象的唯一性变了。
这里具体的原因很复杂,我最近看了点深拷贝的知识,所以只是知其然不知其之所以然。(所以学习是需要不断进行的!加油诸位。)
这里我贴一下别的经验吧:(感兴趣的可以实现一下!)
为什么序列化可以破坏单例了?
答:序列化会通过反射调用无参数的构造方法创建一个新的对象。
这个东西目前超出了我的能力范围了,但也是去查看源码得出来的,就是序列化(serializable)和反序列化(externalizable)接口的详细情况了。但是有一点,它也是通过反射来做的的,所以可以看出**反射(reflect)**是一种非常强大和危险的技术了。
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!