深度解析单例模式

饥汉模式

package com.cz.single;  /**  * @author 卓亦苇  * @version 1.0  * 2023/3/11 21:31  */ public class Hungry {      private byte[] data1 = new byte[1024];     private byte[] data2 = new byte[1024];     private byte[] data3 = new byte[1024];     private byte[] data4 = new byte[1024];     private Hungry(){      }      private final static Hungry hungry = new Hungry();     public static Hungry getInstance(){         return hungry;     }      public static void main(String[] args) {      .   Hungry.getInstance();     } } 

会浪费内存,执行代码,其4个对象已经被创建,浪费空间。

懒汉式单例

package com.cz.single;  /**  * @author 卓亦苇  * @version 1.0  * 2023/3/11 21:35  */ public class LazyMan {      private LazyMan(){         System.out.println(Thread.currentThread().getName()+"  OK");     }     private volatile static LazyMan lazyMan;      public static LazyMan getLazyMan(){         if ((lazyMan==null)){             synchronized (LazyMan.class){                 if (lazyMan==null){                     lazyMan = new LazyMan();                 }             }         }         return lazyMan;     }      public static void main(String[] args) {          for (int i = 0; i < 10; i++) {             new Thread(()->{                 LazyMan.getLazyMan();             }).start();         }     } } 

懒汉式为使用该对象才创建新对象,但是初始代码有问题,单线程初始没有问题,多线程会造成,非单例。

解决办法,首先加锁,先判断对象是否为空,如果为空则将class对象进行上锁,然后需再判断,锁是否为空,如果为空再创建新对象。

同步代码块简单来说就是将一段代码用一把锁给锁起来, 只有获得了这把锁的线程才访问, 并且同一时刻, 只有一个线程能持有这把锁, 这样就保证了同一时刻只有一个线程能执行被锁住的代码。第二层,是因为使用同步代码块才加上的,有的可能过了第一个if,没到同步代码块

为双层检测的懒汉式单例,也称DCL懒汉式

第二个问题

lazyMan = new LazyMan(); 

代码为非原子性操作

创建新对象的底层操作分为3步

1.分配内存空间
2、执行构造方法,初始化对象
3、把这个对象指向这个空间
但如果不是原子操作,那132的状况式可能发现的,如果在A还没完成构造是,线程B进来,则不会执行if语句,发生错误

让lazyMan加上volatile参数

反射会破坏单例模式

public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {  //        for (int i = 0; i < 10; i++) { //            new Thread(()->{ //                LazyMan.getLazyMan(); //            }).start(); //        }         LazyMan lazyMan1 = LazyMan.getLazyMan();         //获得空参构造器         Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);         //反射的setAccessible(true)参数,无视私有构造器         declaredConstructor.setAccessible(true);         //通过反射建造对象         LazyMan lazyMan2 = declaredConstructor.newInstance();          System.out.println(lazyMan1);         System.out.println(lazyMan2);     } 

深度解析单例模式

解决办法,在无参构造器添加异常

private LazyMan(){     synchronized (LazyMan.class){         if (lazyMan!=null){             throw new RuntimeException("不要试图反射破坏");         }     }     System.out.println(Thread.currentThread().getName()+"  OK"); } 

深度解析单例模式

但依然存在问题, if (lazyMan!=null)为非原子性操作,依然存在两个反射对象导致出现非单例的状况

//可以采用红绿灯解决,定义一个密钥 private static boolean key = false; private LazyMan(){     synchronized (LazyMan.class){         if (key==false){             key=true;         }else {                  throw new RuntimeException("不要试图反射破坏");                       }     }     System.out.println(Thread.currentThread().getName()+"  OK"); } 

但是如果仍然用反射破环,假设获取到了密钥的情况

//通过反射修改静态参数 Field key1 = LazyMan.class.getDeclaredField("key"); key1.setAccessible(true);  //获得空参构造器 Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null); //反射的setAccessible(true)参数,无视私有构造器 declaredConstructor.setAccessible(true); //通过反射建造对象 LazyMan lazyMan1 = declaredConstructor.newInstance();  //修改对象的密钥参数 key1.set(lazyMan1,false);  LazyMan lazyMan2 = declaredConstructor.newInstance();  System.out.println(lazyMan1); System.out.println(lazyMan2); 

单例仍然会被破坏

真实有效的方式枚举

package com.cz.single;  import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException;  /**  * @author 卓亦苇  * @version 1.0  * 2023/3/14 16:26  */ public enum EnumSingle {      INSTANCE;      public EnumSingle getInstance(){         return INSTANCE;     } } class Test{     public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {         EnumSingle instance1 = EnumSingle.INSTANCE;         //EnumSingle instance2 = EnumSingle.INSTANCE;         Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);         declaredConstructor.setAccessible(true);         EnumSingle instance2 = declaredConstructor.newInstance();          System.out.println(instance1);         System.out.println(instance2);     } } 

深度解析单例模式

至此才得以真正解决

发表评论

相关文章