Skip to content
DAILY QUOTE

“ ”

饿汉式 DCL懒汉式 ,探究!

饿汉式

java
package com.mystpet.dianli;  
  
//假设你的项目非常庞大,JVM 启动时加载了这个 Hungry 类。因为它是“饿汉式”,所以随着类的加载,那个带有 4MB 累赘数据的 HUNGRY 实例就被立刻创建出来了,死死地占用了内存。  
//但是! 如果你的程序在后续的运行过程中,从头到尾都没有调用过 Hungry.getInstance()(可能因为某些业务逻辑没走到这一步),那么这 4MB 的内存空间就被白白浪费了,直到程序结束才会被回收。  
  
  
public class Hungry {  
    private byte[] data1=new byte[1024*1024];  
    private byte[] data2=new byte[1024*1024];  
    private byte[] data3=new byte[1024*1024];  
    private byte[] data4=new byte[1024*1024];  
  
    private Hungry(){  
    }  
  
    private final static Hungry HUNGRY=new Hungry();  
    public static Hungry getInstance(){  
        return HUNGRY;  
    }  
  
}

懒汉式单例

java
public class LazyManV1 {
    private static LazyManV1 lazyMan;

    // 1. 私有化构造器,不让别人直接 new,保证单例只能由自己的方法创造
    private LazyManV1() {
        System.out.println(Thread.currentThread().getName() + " 创建了实例");
    }

    // 2. 提供获取实例的方法
    public static LazyManV1 getInstance() {
        if (lazyMan == null) {
            lazyMan = new LazyManV1();
        }
        return lazyMan;
    }
}

缺点: 如果有两个线程同时运行到 if (lazyMan == null),它们都会觉得目前没有实例,然后各自 new 了一个。单例直接被破功。

java
public class LazyManV2 {
    //必须加 volatile!防止指令重排导致的半初始化问题
    private volatile static LazyManV2 lazyMan;

    private LazyManV2() {
        System.out.println(Thread.currentThread().getName() + " 创建了实例");
    }


// 双重检测锁模式的 懒汉式单例 DCL懒汉式
    public static LazyManV2 getInstance() {  //静态方法,没有实例对象可以锁,只能锁全局唯一的类对象
        if (lazyMan == null) { // 第一重检测
            synchronized (LazyManV2.class) { // 加锁
                if (lazyMan == null) { // 第二重检测
                    lazyMan = new LazyManV2(); // 此时才是安全的
                }
            }
        }
        return lazyMan;
    }
}

缺点: 虽然完美挡住了多线程,但黑客可以使LazyManV2.class.getDeclaredConstructor() 强行破解私有构造器,无限 new 新对象。

java
public class LazyManV3 {
    private volatile static LazyManV3 instance;
    // 【关键升级2】加入布尔值暗号,默认是 false
    private static boolean flag = false;

    private LazyManV3() {
        synchronized (LazyManV3.class) {
            if (flag == false) {
                flag = true; // 第一次创建,把暗号改为 true
            } else {
                // 第二次还想通过反射进来?直接引爆!
                throw new RuntimeException("不用试图使用反射破坏单例!");
            }
        }
    }

    public static LazyManV3 getInstance() {
        if (instance == null) {
            synchronized (LazyManV3.class) {
                if (instance == null) {
                    instance = new LazyManV3();
                    /** * 1.分配内存空间 
                        * 2.执行构造方法,初始化内存对象 
                        * 3.把这个对象指向这个空间 
                    */
                }
            }
        }
        return instance;
    }
    
    // 反射!  
    public static void main(String[] args) throws Exception {  
        //LazyMan instance = LazyMan.getInstance();  
  
        Field flag = LazyMan.class.getDeclaredField("flag");  
        flag.setAccessible(true);  
  
        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);  //定位到那个被私有化隐藏起来的构造器
        declaredConstructor.setAccessible(true);  //强行夺权
        LazyMan instance = declaredConstructor.newInstance();  //创建对象
  
        flag.set(instance, false);  //拿到了类里面的私有变量 `flag`,同样用 `setAccessible(true)` 撬开它的锁,然后强行把内存里这个变量的值给改写了
  
        LazyMan instance2 = declaredConstructor.newInstance();  
        System.out.println(instance);  
        System.out.println(instance2);  
    }  
}
}

静态内部类

java
package com.mystpet.dianli;  
  
// 静态内部类  
public class Holder {  
  
    private Holder() {  
    }  
  
    public static Holder getInstance() {  
        return InnerClass.HOLDER;  
    }  
  
    public static class InnerClass {    //静态内部类,调用才会加载
        private static final Holder HOLDER = new Holder();  
    }  
  
}

单例保证: JVM 在底层规定死了:一个类的初始化过程,天生就是单线程互斥的。 当 10000 个线程同时触发去加载 InnerClass 时,JVM 会自动在底层挂上一把隐形的超级大锁,只放一个线程进去把 HOLDER 创建好,其他线程必须在外面干等。等创建好了,大家直接拿现成的就行。

缺点: 依然防不住反射

枚举

查看反射的newInstance()方法,发现不能对枚举类使用

测试

java
package com.mystpet.dianli;  
  
import java.lang.reflect.Constructor;  
import java.lang.reflect.InvocationTargetException;  
  
// enum 是一个什么? 本身也是一个Class类  
  
  
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;  
  
       // 在这里依然用 getDeclaredConstructor(null),系统会直接报错 NoSuchMethodException(找不到无参构造)  
        Constructor<EnumSingle> declaredConstructor=EnumSingle.class.getDeclaredConstructor(String.class,int.class);  
        EnumSingle instance2=declaredConstructor.newInstance();  
        System.out.println(instance1);  
        System.out.println(instance2);  
    }  
}
java
//反射底层代码
// 如果检查到你这个类是一个 Enum
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
    // 直接抛出异常
    throw new IllegalArgumentException("Cannot reflectively create enum objects");