标签:不能 影响 模板 也有 oca 命令行 现在 玩转 import
饿汉式 DCL懒汉式,深究!
饿汉式:顾名思义很饿:在类加载的时候,直接初始化对象
缺点:很浪费资源,因为对象没有被使用,但是已经初始化在内存了
比如:有下面这样的数组,会很浪费资源
package com.zxh.single; /** * 饿汉式:顾名思义很饿 * 1、在类加载的时候,直接初始化对象 * 2、很浪费资源,因为对象没有被使用,但是已经初始化在内存了 * 比如:有下面这样的数组,会很浪费资源 */ public class Hungry { private byte[] buffer1 = new byte[1024*1024]; private byte[] buffer2 = new byte[1024*1024]; private byte[] buffer3 = new byte[1024*1024]; private byte[] buffer4 = new byte[1024*1024]; // 构造器私有化 private Hungry(){ } // 直接初始化对象 private final static Hungry HUNGRY = new Hungry(); public static Hungry getInstance(){ return HUNGRY; } public static void main(String[] args) { Hungry hungry1 = Hungry.getInstance(); Hungry hungry2 = Hungry.getInstance(); System.out.println(hungry1); System.out.println(hungry2); } }
浪费资源所以就有了懒汉式创建单例模式
package com.zxh.single; /** * 懒汉式创建单例模式 */ public class LazyMan { // 构造器私有化 private LazyMan(){ } private static LazyMan LAZY_MAN; public static LazyMan getInstance(){ if(LAZY_MAN == null){ // 如果为空,初始化对象 LAZY_MAN = new LazyMan(); } return LAZY_MAN; // 并返回 } public static void main(String[] args) { LazyMan lazyMan1 = LazyMan.getInstance(); LazyMan lazyMan2 = LazyMan.getInstance(); System.out.println(lazyMan1); System.out.println(lazyMan2); } }
package com.zxh.single; /** * 懒汉式创建单例模式 */ public class LazyMan { // 构造器私有化 private LazyMan(){ System.out.println(Thread.currentThread().getName() + " OK"); } private static LazyMan LAZY_MAN; public static LazyMan getInstance(){ if(LAZY_MAN == null){ // 如果为空,初始化对象 LAZY_MAN = new LazyMan(); } return LAZY_MAN; // 并返回 } public static void main(String[] args) { for (int i = 0; i < 10; i++) { new Thread(()->{ LazyMan.getInstance(); }).start(); } } }
可以看到经过了4次构造方法,也就是创建了4个不同的对象。
那么如何解决多线程的并发问题?使用DCL (双重检测锁)懒汉式
DCL就是双重检测锁:解决并发问题
修改getInstance
这个方法
public static LazyMan getInstance(){ synchronized (LazyMan.class){ // 直接锁class模板 if(LAZY_MAN == null){ // 如果为空,初始化对象 LAZY_MAN = new LazyMan(); } } return LAZY_MAN; // 并返回 } public static void main(String[] args) { for (int i = 0; i < 10; i++) { new Thread(()->{ LazyMan.getInstance(); }).start(); } }
缺点:影响效率,因为每个线程都需要同步等待。
解决:在外面增加判断如果对象已经创建,那么直接返回
解决同步的效率问题
// DCL 双重检测锁 public static LazyMan getInstance(){ if (LAZY_MAN == null) { // 第一重检测 synchronized (LazyMan.class){ // 直接锁class模板,锁 if(LAZY_MAN == null){ // 如果为空,初始化对象,第二重检测 LAZY_MAN = new LazyMan(); } } } return LAZY_MAN; // 并返回 } public static void main(String[] args) { for (int i = 0; i < 10; i++) { new Thread(()->{ LazyMan.getInstance(); }).start(); } }
// DCL 双重检测锁 public static LazyMan getInstance(){ if (LAZY_MAN == null) { // 第一重检测 synchronized (LazyMan.class){ // 直接锁class模板,锁 if(LAZY_MAN == null){ // 如果为空,初始化对象,第二重检测 /** * 但是真的安全吗?不安全 * 因为初始化对象不是原子操作,在极端情况下会进行指令重排 * 初始化对象的时候,不要以为只有一行代码,但是执行的时候会分成3步 * 1、分配内存空间 * 2、执行构造方法,初始化对象 * 3、把这个对象指向这个空间 * * 比如:我们希望执行的顺序为 123, * 假如A线程进入,经过指令重排执行顺序为 132,当执行到13,还没有执行2的时候,内存空间是分配了也指向了这个空间,但是对象是空的 * 此时B线程进入了,发现对象已经分配了空间,直接返回了,就会造成空指针 */ LAZY_MAN = new LazyMan(); } } } return LAZY_MAN; // 并返回 }
/** * 懒汉式创建单例模式 */ public class LazyMan { // 构造器私有化 private LazyMan(){ System.out.println(Thread.currentThread().getName() + " OK"); } private volatile static LazyMan LAZY_MAN; // DCL 双重检测锁 public static LazyMan getInstance(){ if (LAZY_MAN == null) { // 第一重检测 synchronized (LazyMan.class){ // 直接锁class模板,锁 if(LAZY_MAN == null){ // 如果为空,初始化对象,第二重检测 LAZY_MAN = new LazyMan(); } } } return LAZY_MAN; // 并返回 } public static void main(String[] args) { for (int i = 0; i < 10; i++) { new Thread(()->{ LazyMan.getInstance(); }).start(); } } }
package com.zxh.single; // 静态内部类创建 public class Holder { private Holder(){ } public static InnerClass getInstance(){ return InnerClass.INNER_CLASS; } // 静态内部类,在程序加载时,并不会被初始化,所以不会浪费资源 private static class InnerClass{ private final static InnerClass INNER_CLASS = new InnerClass(); } public static void main(String[] args) { InnerClass innerClass1 = Holder.getInstance(); InnerClass innerClass2 = Holder.getInstance(); System.out.println(innerClass1); System.out.println(innerClass2); } }
现在DCL 是目前我们认为最厉害的
但是在反射面前,一切都时候浮云
会创建两个不同的对象
package com.zxh.single; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; /** * 懒汉式创建单例模式 */ public class LazyMan { // 构造器私有化 private LazyMan(){ } private volatile static LazyMan LAZY_MAN; // DCL 双重检测锁 public static LazyMan getInstance(){ if (LAZY_MAN == null) { // 第一重检测 synchronized (LazyMan.class){ // 直接锁class模板,锁 if(LAZY_MAN == null){ // 如果为空,初始化对象,第二重检测 LAZY_MAN = new LazyMan(); } } } return LAZY_MAN; // 并返回 } // NoSuchMethodException:没有这个方法 public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { LazyMan lazyMan1 = LazyMan.getInstance(); Constructor<LazyMan> lazyManConstructor = LazyMan.class.getDeclaredConstructor(null); LazyMan lazyMan2 = lazyManConstructor.newInstance(null); System.out.println(lazyMan1); System.out.println(lazyMan2); } }
// 构造器私有化 private LazyMan(){ synchronized (LazyMan.class){ if(LAZY_MAN != null) throw new RuntimeException("不要试图利用反射破坏单例"); } }
// NoSuchMethodException:没有这个方法 public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { Constructor<LazyMan> lazyManConstructor = LazyMan.class.getDeclaredConstructor(null); LazyMan lazyMan1 = lazyManConstructor.newInstance(null); LazyMan lazyMan2 = lazyManConstructor.newInstance(null); System.out.println(lazyMan1); System.out.println(lazyMan2); }
private static boolean flag = false:注意必须使用static修饰,才能是全局的变量
package com.zxh.single; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; /** * 懒汉式创建单例模式 */ public class LazyMan { private static boolean flag = false; // 增加一个标志,来判别是否已经创建了对象 // 构造器私有化 private LazyMan(){ synchronized (LazyMan.class){ if(flag == false){ // 第一次进入 flag = true; // 表示已将创建了对象 }else{ throw new RuntimeException("不要试图利用反射破坏单例"); } } } private volatile static LazyMan LAZY_MAN; // DCL 双重检测锁 public static LazyMan getInstance(){ if (LAZY_MAN == null) { // 第一重检测 synchronized (LazyMan.class){ // 直接锁class模板,锁 if(LAZY_MAN == null){ // 如果为空,初始化对象,第二重检测 LAZY_MAN = new LazyMan(); } } } return LAZY_MAN; // 并返回 } // NoSuchMethodException:没有这个方法 public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { Constructor<LazyMan> lazyManConstructor = LazyMan.class.getDeclaredConstructor(null); LazyMan lazyMan1 = lazyManConstructor.newInstance(null); LazyMan lazyMan2 = lazyManConstructor.newInstance(null); System.out.println(lazyMan1); System.out.println(lazyMan2); } }
// NoSuchMethodException:没有这个方法 public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException, ClassNotFoundException { Constructor<LazyMan> lazyManConstructor = LazyMan.class.getDeclaredConstructor(null); LazyMan lazyMan1 = lazyManConstructor.newInstance(null); Field flag = LazyMan.class.getDeclaredField("flag"); flag.setAccessible(true); // 关闭安全监测锁,提高创建对象的效率 flag.set(lazyMan1, false); LazyMan lazyMan2 = lazyManConstructor.newInstance(null); System.out.println(lazyMan1); System.out.println(lazyMan2); }
哪怕这个标志的字段是加密的,也有可能会被反编译破解,从而获取字段信息。
所以说魔高一尺,道高一丈!
但是我们还可以利用枚举创建,因为它的底层就是不允许通过反射创建对象的
package com.zxh.single; public enum EnumSingle { INSTANCE; public EnumSingle getInstance(){ return INSTANCE; } public static void main(String[] args) { EnumSingle instance1 = EnumSingle.INSTANCE; EnumSingle instance2 = EnumSingle.INSTANCE; System.out.println(instance1 == instance2); } }
1、进入newInstance()方法
2、发现如果是枚举类,就抛出不能使用反射创建枚举类异常
package com.zxh.single; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; public enum EnumSingle { INSTANCE; public EnumSingle getInstance(){ return INSTANCE; } public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { EnumSingle instance1 = EnumSingle.INSTANCE; Constructor<EnumSingle> constructor = EnumSingle.class.getDeclaredConstructor(null); EnumSingle instance2 = constructor.newInstance(null); System.out.println(instance1 == instance2); } }
发现抛出异常时没有这个方法,和想象中会抛出的异常不同,为什么?
是没有这个构造器吗?
1、通过编译生成的target包中对应的class文件查看
发现有这个构造器
2、通过反编译class文件查看,进入指定的目录在命令行输入:javap -p EnumSingle.class
发现也有这个构造器
难道是idea和jdk骗了我们?
3、使用专业的软件反编译
jad百度网盘下载 提取码: 9fpa
1)进入指定目录输入,jad -sjava EnumSingle.class
2)编译得到的文件中可以发现没有空构造,但是有一个有参构造
package com.zxh.single; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; public enum EnumSingle { INSTANCE; public EnumSingle getInstance(){ return INSTANCE; } public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { EnumSingle instance1 = EnumSingle.INSTANCE; Constructor<EnumSingle> constructor = EnumSingle.class.getDeclaredConstructor(String.class, int.class); EnumSingle instance2 = constructor.newInstance(null); System.out.println(instance1 == instance2); } }
枚举类是创建单例模式最安全的,推荐使用!
标签:不能 影响 模板 也有 oca 命令行 现在 玩转 import
原文地址:https://www.cnblogs.com/zxhbk/p/13028223.html