当前位置:   article > 正文

设计模式之单例模式

设计模式之单例模式

五种实现方式:

方式一:饿汉式
  1. //饿汉式
  2. public class Singleton1 implements Serializable {
  3. private static final Singleton1 SINGLETON_TEST=new Singleton1();
  4. //构造私有
  5. private Singleton1() {
  6. System.out.println("私有构造方法");
  7. }
  8. public static Singleton1 getInstance(){
  9. return SINGLETON_TEST;
  10. }
  11. public static void otherMotherd(){
  12. System.out.println("其他方法");
  13. }
  14. }

测试:
 

  1. public class SingleTest {
  2. public static void main(String[] args) {
  3. Singleton1.otherMotherd();//检测是饿汉式
  4. //私有构造方法
  5. //其他方法
  6. System.out.println(Singleton1.getInstance());//Singleton1@4d7e1886
  7. System.out.println(Singleton1.getInstance());//Singleton1@4d7e1886
  8. //以上两个实例为同一个对象
  9. }
  10. }

结果:

 单例被破坏的情况: 
  1. 序列化和反序列化

当单例类实现了Serializable接口时,对象被序列化后再反序列化时会创建新的实例。为了避免这种情况,可以通过重写readResolve()方法来返回单例实例。

测试反序列化后的对象和原始对象是否一致:

  1. /**
  2. * 实现序列化接口后可能破坏单例模式
  3. */
  4. @Test
  5. void test5() throws Exception {
  6. //将对象写入文件中
  7. ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("single1.ser"));
  8. oos.writeObject(Singleton1.getInstance());
  9. oos.close();
  10. //从文件中读取对象
  11. ObjectInputStream ois=new ObjectInputStream(new FileInputStream("single1.ser"));
  12. Singleton1 single = (Singleton1) ois.readObject();
  13. ois.close();
  14. System.out.println("original:"+Singleton1.getInstance().hashCode());
  15. System.out.println("DeSerializable:"+single.hashCode());
  16. }

结果:

预防:

  1. public class Singleton implements Serializable {
  2. private static final Singleton instance = new Singleton();
  3. private Singleton() {}
  4. public static Singleton getInstance() {
  5. return instance;
  6. }
  7. protected Object readResolve() {
  8. return instance;
  9. }
  10. }
  1. 反射机制:

通过反射可以调用类的私有构造方法,从而创建多个实例。为了防止通过反射破坏单例模式,可以在构造方法中添加逻辑判断,确保只创建一个实例。

反射破坏单例:

  1. @Test
  2. void test() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
  3. Singleton1 instance = Singleton1.getInstance();
  4. Constructor<? extends Singleton1> constructor = instance.getClass().getDeclaredConstructor();
  5. constructor.setAccessible(true);
  6. Singleton1 singleton1 = constructor.newInstance();
  7. System.out.println(instance==singleton1);
  8. }

 结果:

预防:在构造方法处进行判断

  1. //饿汉式
  2. public class Singleton2 implements Serializable {
  3. private static final Singleton2 SINGLETON_TEST=new Singleton2();
  4. //构造私有
  5. private Singleton2() {
  6. if (SINGLETON_TEST!=null){
  7. throw new IllegalStateException("对象已经创建");
  8. }
  9. System.out.println("私有构造方法");
  10. }
  11. public static Singleton2 getInstance(){
  12. return SINGLETON_TEST;
  13. }
  14. public static void otherMotherd(){
  15. System.out.println("其他方法");
  16. }
  17. }

 以上防止反射破坏单例测试:

  1. /**
  2. *防止单例被破坏
  3. */
  4. @Test
  5. void test3() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
  6. Singleton2 instance = Singleton2.getInstance();
  7. Constructor<? extends Singleton2> constructor = instance.getClass().getDeclaredConstructor();
  8. constructor.setAccessible(true);
  9. Singleton2 singleton2 = constructor.newInstance();
  10. System.out.println(instance==singleton2);
  11. }

结果:

  1. 类加载器:

如果使用不同的类加载器加载同一个类,也可能导致创建多个实例。为了解决这个问题,可以在获取实例时指定类加载器,确保只有一个实例被创建。

测试使用不同的类加载器导致同一个类创建多个实例的情况:
 

  1. public static void main(String[] args) throws Exception {
  2. CurstomLoader loader1=new CurstomLoader();
  3. CurstomLoader loader2=new CurstomLoader();
  4. Class<?> single1 = loader1.loadClass("com.example.singletonmodle.single.Singleton1");
  5. Class<?> single2 = loader2.loadClass("com.example.singletonmodle.single.Singleton1");
  6. Singleton1 instance1 = (Singleton1) single1.newInstance();
  7. Singleton1 instance2= (Singleton1) single2.newInstance();
  8. System.out.println("loader1:"+instance1.hashCode());
  9. System.out.println("loader2:"+instance2.hashCode());
  10. }

 

  1. public class Singleton {
  2. private static Singleton instance = new Singleton();
  3. private Singleton() {}
  4. public static Singleton getInstance(ClassLoader classLoader) {
  5. synchronized (Singleton.class) {
  6. if (instance == null) {
  7. try {
  8. Class<?> clazz = classLoader.loadClass(Singleton.class.getName());
  9. instance = (Singleton) clazz.newInstance();
  10. } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
  11. e.printStackTrace();
  12. }
  13. }
  14. }
  15. return instance;
  16. }
  17. }
  1. 通过unsafe破坏单例:目前没有预防的方法。

 总结:多线程破坏单例使用DCL+voliatle预防,反射破坏单例使用在构造方法处检查对象是否存在进行预防,实现序列化接口破坏单例使用实现readResolve方法,返回已经创建的单例对象。unsafe目前还没有预防的方式。

方式二:枚举类

  1. public enum Singleton3 {
  2. INSTANCE;
  3. private Singleton3(){
  4. System.out.println("枚举类的私有构造方法");
  5. }
  6. @Override
  7. public String toString() {
  8. //getClass().getName()返回该对象的类名,Integer.toHexString(hashCode())返回该对象的哈希码的十六进制表示。
  9. return getClass().getName()+"@"+Integer.toHexString(hashCode());
  10. }
  11. public Singleton3 getInstance(){
  12. return INSTANCE;
  13. }
  14. public void otherMethod(){
  15. System.out.println("其他方法");
  16. }
  17. }

测试:

 枚举实现的单例可以预防反序列化破坏单例

枚举也可以预防反射破坏单例

枚举类的构造方法不是无参构造,有两个参数。

但是枚举无法预防unsafe破坏单例模式。

方式三:懒汉式
 
  1. //懒汉式
  2. public class Singleton4 {
  3. private static Singleton4 SINGLETON_TEST;
  4. //构造私有
  5. private Singleton4() {
  6. System.out.println("私有构造方法");
  7. }
  8. public static Singleton4 getInstance(){
  9. if (SINGLETON_TEST==null){
  10. SINGLETON_TEST=new Singleton4();
  11. }
  12. return SINGLETON_TEST;
  13. }
  14. public static void otherMotherd(){
  15. System.out.println("其他方法");
  16. }
  17. }

测试:

以上单例模式在多线程下存在单例被破坏的可能。

  1. 多线程环境下未处理好并发访问:

如果在多线程环境下,多个线程同时尝试获取单例实例,可能会导致创建多个实例的情况。可以使用DCL(双重检测来确保只有一个实例被创建,DCL需要配合volatile使用,确保可见性)。懒汉式

  1. public class Singleton {
  2. private static final Singleton instance = new Singleton();
  3. private Singleton() {}
  4. public static Singleton getInstance() {
  5. return instance;
  6. }
  7. }
  8. public class Main {
  9. public static void main(String[] args) {
  10. // 创建多个线程同时获取单例实例
  11. Thread thread1 = new Thread(() -> {
  12. Singleton singleton = Singleton.getInstance();
  13. System.out.println("Thread 1: " + singleton.hashCode());
  14. });
  15. Thread thread2 = new Thread(() -> {
  16. Singleton singleton = Singleton.getInstance();
  17. System.out.println("Thread 2: " + singleton.hashCode());
  18. });
  19. thread1.start();
  20. thread2.start();
  21. }
  22. }

预防:DCL+voliatle ,也就是第四种实现方式。

方式四:DCL+voliatle
  1. public class Singleton {
  2. private static volatile Singleton instance; //保证共享变量的有序性
  3. private Singleton() {}
  4. public static Singleton getInstance() {
  5. if (instance == null) {
  6. synchronized (Singleton.class) {
  7. if (instance == null) {
  8. instance = new Singleton();
  9. }
  10. }
  11. }
  12. return instance;
  13. }
  14. }


方式五:静态内部类,懒汉式:
  1. /静态内部类方式
  2. public class Singleton6 {
  3. private static Singleton6 SINGLETON_TEST=null;
  4. //构造私有
  5. private Singleton6() {
  6. System.out.println("私有构造方法");
  7. }
  8. private static class Holder{
  9. static Singleton6 INSTANCE=new Singleton6();
  10. }
  11. public static Singleton6 getInstance(){
  12. return Holder.INSTANCE;
  13. }
  14. public static void otherMotherd() {
  15. System.out.println("其他方法");
  16. }
  17. }

JDK中单例的体现方式:
单例模式一般在jdk的一些库中见到,自己不要乱用单例模式,很容易用错。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/花生_TL007/article/detail/465944
推荐阅读
相关标签
  

闽ICP备14008679号