5.0 静态内部类/登记式
是否 Lazy 初始化:是
是否多线程安全:是
实现难度:一般
描述:这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。
这种方式同样利用了 classloader 机制来保证初始化 instance 时只有一个线程,它跟第 3 种方式不同的是:第 3 种方式只要 Singleton 类被装载了,那么 instance 就会被实例化(没有达到 lazy loading 效果),而这种方式是 Singleton 类被装载了,instance 不一定被初始化。
因为 SingletonHolder 类没有被主动使用,只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance。想象一下,如果实例化 instance 很消耗资源,所以想让它延迟加载,另外一方面,又不希望在 Singleton 类加载时就实例化,因为不能确保 Singleton 类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化 instance 显然是不合适的。这个时候,这种方式相比第 3 种方式就显得很合理。
静态内部类:我们在一个类里面再写一个静态的内部类 称之为 静态内部类
//静态内部类:我们在一个类里面再写一个静态的内部类 称之为 静态内部类 public class Holder { private Holder(){ } //调用静态内部类 public static Holder getInstance(){ return Innerclass.HOLDER; } //静态内部类 static 代表静态 final 不可变 public static class Innerclass{ private static final Holder HOLDER =new Holder(); } }
6.0 反射破坏单例模式
此处用反射和懒汉式为例:
其实上述这些都不是安全的,我们都可以采用内部类进行破坏;
//懒汉式单例 public class LazyMan { //私有构造 private LazyMan() { //打印下 System.out.println(Thread.currentThread().getName() + "老闫牛逼"); } //----------------volatile原子性----------------------------- //使其具有原子性操作 private volatile static LazyMan lazyMan; //--------------------------------------------- //双重检测锁模式的 懒汉式单例 DCL懒汉式 public static LazyMan getInstance() { if (lazyMan == null) { synchronized (LazyMan.class) { if (lazyMan == null) { lazyMan = new LazyMan(); } } } if (lazyMan == null) { lazyMan = new LazyMan(); } return lazyMan; } //************************************************** //反射破坏单例 public static void main(String[] args) throws Exception { //执行第一次 LazyMan instance=LazyMan.getInstance(); //创建空的构造器 Constructor constructor = LazyMan.class.getDeclaredConstructor(null); //默认为false 无视私有构造器(private)也可以理解为现在private public constructor.setAccessible(true); //执行第二次 LazyMan lazyMan1 = constructor.newInstance(); System.out.println("instance==>"+instance); System.out.println("lazyMan1==>"+lazyMan1); } //**************************************************
运行结果
main老闫牛逼 main老闫牛逼 instance==>com.example.democrud.democurd.single.LazyMan@193b845 lazyMan1==>com.example.democrud.democurd.single.LazyMan@817b38
他们已经不是一个值;单例模式的话应该是一个值;
那么如何防止此次单例模式的破坏呢?
我们加入异常他如果强行闯入我们就抛出异常;
//懒汉式单例 public class LazyMan { //********************************* //私有构造 private LazyMan() { //防止反射此处加入锁 三层锁 synchronized (LazyMan.class){ if (lazyMan!=null){ throw new RuntimeException("请您不要试图使用反射破坏锁异常"); } } //********************************* System.out.println(Thread.currentThread().getName() + "老闫牛逼"); } //----------------volatile原子性----------------------------- //使其具有原子性操作 private volatile static LazyMan lazyMan; //--------------------------------------------- //双重检测锁模式的 懒汉式单例 DCL懒汉式 public static LazyMan getInstance() { if (lazyMan == null) { synchronized (LazyMan.class) { if (lazyMan == null) { lazyMan = new LazyMan(); } } } if (lazyMan == null) { lazyMan = new LazyMan(); } return lazyMan; } //反射破坏单例 public static void main(String[] args) throws Exception { //执行第一次 LazyMan instance=LazyMan.getInstance(); //创建空的构造器 Constructor constructor = LazyMan.class.getDeclaredConstructor(null); //默认为false 无视私有构造器(private)也可以理解为现在private public constructor.setAccessible(true); //执行第二次 LazyMan lazyMan1 = constructor.newInstance(); System.out.println("instance==>"+instance); System.out.println("lazyMan1==>"+lazyMan1); } }
即使这样我们也只能解决 LazyMan instance=LazyMan.getInstance();他的创建对象的方法;
那么我们如果不用他去创建的话,那么单例模式再次被破话!!!
//懒汉式单例 public class LazyMan { //********************************* //私有构造 private LazyMan() { //防止反射此处加入锁 三层锁 synchronized (LazyMan.class){ if (lazyMan!=null){ throw new RuntimeException("请您不要试图使用反射破坏锁异常"); } } //********************************* System.out.println(Thread.currentThread().getName() + "老闫牛逼"); } //----------------volatile原子性----------------------------- //使其具有原子性操作 private volatile static LazyMan lazyMan; //--------------------------------------------- //双重检测锁模式的 懒汉式单例 DCL懒汉式 public static LazyMan getInstance() { if (lazyMan == null) { synchronized (LazyMan.class) { if (lazyMan == null) { lazyMan = new LazyMan(); } } } if (lazyMan == null) { lazyMan = new LazyMan(); } return lazyMan; } //反射破坏单例 public static void main(String[] args) throws Exception { //执行第一次 // LazyMan instance=LazyMan.getInstance(); //创建空的构造器 Constructor constructor = LazyMan.class.getDeclaredConstructor(null); //默认为false 无视私有构造器(private)也可以理解为现在private public constructor.setAccessible(true); //执行第二次 LazyMan lazyMan1 = constructor.newInstance(); LazyMan lazyMan2 = constructor.newInstance(); System.out.println("lazyMan2==>"+lazyMan2); System.out.println("lazyMan1==>"+lazyMan1); }
我们这个时候可以设置一个变量;利用变量进行操作
//懒汉式单例 public class LazyMan { //leees 可以是一串密钥或其他东西 private static boolean leees=false; //私有构造 private LazyMan() { synchronized (LazyMan.class){ if (leees==false){ leees=true; }else { throw new RuntimeException("请您不要试图使用反射破坏锁异常"); } } System.out.println(Thread.currentThread().getName() + "老闫牛逼"); } //----------------volatile原子性----------------------------- //使其具有原子性操作 private volatile static LazyMan lazyMan; //--------------------------------------------- //双重检测锁模式的 懒汉式单例 DCL懒汉式 public static LazyMan getInstance() { if (lazyMan == null) { synchronized (LazyMan.class) { if (lazyMan == null) { lazyMan = new LazyMan(); } } } if (lazyMan == null) { lazyMan = new LazyMan(); } return lazyMan; } //反射破坏单例 public static void main(String[] args) throws Exception { //执行第一次 // LazyMan instance=LazyMan.getInstance(); //创建空的构造器 Constructor constructor = LazyMan.class.getDeclaredConstructor(null); //默认为false 无视私有构造器(private)也可以理解为现在private public constructor.setAccessible(true); //执行第二次 LazyMan lazyMan1 = constructor.newInstance(); LazyMan lazyMan2 = constructor.newInstance(); System.out.println("lazyMan2==>"+lazyMan2); System.out.println("lazyMan1==>"+lazyMan1); } }
他这个时候就可以对反射进行拦截;
但我们获取这个变量之后,利用变量再应用反射再次对他值进行破坏;再次破坏成功;
//懒汉式单例 public class LazyMan { //leees 可以是一串密钥或其他东西 private static boolean leees=false; //私有构造 private LazyMan() { synchronized (LazyMan.class){ if (leees==false){ leees=true; }else { throw new RuntimeException("请您不要试图使用反射破坏锁异常"); } } } //----------------volatile原子性----------------------------- //使其具有原子性操作 private volatile static LazyMan lazyMan; //--------------------------------------------- //双重检测锁模式的 懒汉式单例 DCL懒汉式 public static LazyMan getInstance() { if (lazyMan == null) { synchronized (LazyMan.class) { if (lazyMan == null) { lazyMan = new LazyMan(); } } } if (lazyMan == null) { lazyMan = new LazyMan(); } return lazyMan; } //反射破坏单例 public static void main(String[] args) throws Exception { //获取他的字段 Field leees1 = LazyMan.class.getDeclaredField("leees"); //对他的值进行破坏 leees1.setAccessible(true); System.out.println("leees1的值"+ JSON.toJSONString(leees1)); //执行第一次 // LazyMan instance=LazyMan.getInstance(); //创建空的构造器 Constructor constructor = LazyMan.class.getDeclaredConstructor(null); //默认为false 无视私有构造器(private)也可以理解为现在private public constructor.setAccessible(true); //执行第二次 LazyMan lazyMan1 = constructor.newInstance(); leees1.set(lazyMan1,false); LazyMan lazyMan2 = constructor.newInstance(); System.out.println("lazyMan2==>"+lazyMan2); System.out.println("lazyMan1==>"+lazyMan1); } }
leees1的值{"accessible":true,"annotatedType":{"annotations":[],"declaredAnnotations":[],"type":"boolean"},"annotations":[],"declaringClass":"com.example.democrud.democurd.single.LazyMan","enumConstant":false,"genericType":"boolean","modifiers":10,"name":"leees","synthetic":false,"type":"boolean"} lazyMan2==>com.example.democrud.democurd.single.LazyMan@4c0bc4 lazyMan1==>com.example.democrud.democurd.single.LazyMan@679bde
我们此时去看看源码如何去解决这个问题?
通过源码分析如果是枚举类型不能使用反射去破坏他;
创建一个枚举 我们去查看下;
//enum 是什么? 是枚举(jdk1.5有的) 本身也是一个class的类 public enum EnumSingle { INSTANNCE; public EnumSingle getInstannce(){ return INSTANNCE; } class Test{ public static void main(String[] args) { EnumSingle single = EnumSingle.INSTANNCE; EnumSingle single2 = EnumSingle.INSTANNCE; System.out.println(single); System.out.println(single2); } } }
我们们正常查看代码是有问题的无法实现我们的枚举对反射的处理;我们查看源码之后也是错误(idea导致);故需要反编译下源代码;
实际是有参的;idea看到的确实无参;(反编译请看7.0扩展)
此处为有参即可正常防止反射的进入;也提示了错误;
7.0扩展:class反编译
java -p 类.class 反编译:
我们使用第二中使用工具jap进行反编译;
地址:https://varaneckas.com/jad/
jad -s java 类.class 即可反编译
反编译成功;
设计模式总目录:https://blog.csdn.net/qq_42055933/article/details/126613801?spm=1001.2014.3001.5501(查看其他章节请点击)