单例模式详解

本文探讨了Java中的单例模式,包括饿汉式、懒汉式、DCL懒汉式(双重检查锁定)以及通过枚举实现的单例。详细分析了各种实现方式的线程安全性和可能的漏洞,特别是反射攻击对单例模式的影响。最后指出,通过枚举实现的单例模式在防止反射破坏方面更为安全。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

今天整理一下单例模式。
单例模式应该是大学课程中,最先接触的设计模式,主要目的是保证生产出来的是一个实例。需要注意三点:
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。
第一种情况:饿汉式

/*
*	饿汉式在不管调没调用方法,都会在类加载的时候事先声明一个对象,是线程安全的,
*	因为每次在类加载的时候对其进行声明,但是会造成资源浪费。
*/
//饿汉式
public class SingleHungry {
    private static SingleHungry singleHungry = new SingleHungry();

    private SingleHungry() {
    }

    public static SingleHungry getInstance(){
        return singleHungry;
    }
}

第二种情况:懒汉式

/*
 * 懒汉式顾名思义,只有用的时候会对其进行声明。但是他是线程不安全的。
 * 不安全的情况看代码注释
 */
 public class SingleLazy {
    private static SingleLazy singleLazy;
    private SingleLazy(){}
   	public  static SingleLazy getInstance(){
        if(singleLazy==null){
                    singleLazy = new SingleLazy();
        }
        return singleLazy;
    }
}
//但是这种情况并不是线程安全的

第三种情况:DCL懒汉式,又叫双重检测锁机制
他并不是线程安全的,由于对对象进行实例化的时候,并不是一个原子性操作具体分析看代码:

public class SingleLazy {
    private static SingleLazy singleLazy;
    private SingleLazy(){
    }
    public  static SingleLazy getInstance(){
        //双重检测锁机制 DCL懒汉式
        if(singleLazy==null){
            synchronized (SingleLazy.class){
                if(singleLazy==null){
                    singleLazy = new SingleLazy();//并不是一个原子性操作
                    /*
                    * 1.分配内存空间
                    * 2.执行构造方法,初始化对象
                    * 3.把这个对象指向空间
                    *   在底层经过指令重排可能会导致直接返回没有初始化完成的对象
                    *   必须加上volatile避免指令重排
                    * */
                }
            }
        }
        return singleLazy;
    }

但是在加上volatile之后,避免强制重排之后,在反射机制下依旧不是一个安全的,仍旧会造成单例模式不安全的情况:

public class SingleLazy {
    private volatile static SingleLazy singleLazy;
    private SingleLazy(){
        synchronized (SingleLazy.class){
            if(singleLazy!= null){
                throw new RuntimeException("不要试图反射破坏异常!");
            }

        }
        System.out.println(Thread.currentThread().getName()+"ok");
    }
    public  static SingleLazy getInstance(){
        //双重检测锁机制 DCL懒汉式
        if(singleLazy==null){
            synchronized (SingleLazy.class){
                if(singleLazy==null){
                    singleLazy = new SingleLazy();//并不是一个原子性操作
                    /*
                    * 1.分配内存空间
                    * 2.执行构造方法,初始化对象
                    * 3.把这个对象指向空间
                    *   在底层经过指令重排可能会导致多线程判断是没有初始化完成的对象
                    *   必须加上volatile避免指令重排
                    * */
                }
            }
        }
        return singleLazy;
    }
    //通过反射依旧能破坏单例模式
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        SingleLazy instance = SingleLazy.getInstance();
        Constructor<SingleLazy> declaredConstructor = SingleLazy.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        SingleLazy instance1= declaredConstructor.newInstance();
        System.out.println(instance);
        System.out.println(instance1);
    }
}

到这里为止,发现在反射能破坏单例模式,在借助反射进行破坏私有构造器的时候,Constructor.java中的newInstance()源码中描述:

在这里插入图片描述
第四种情况:通过枚举来实现单例
在.class文件中发现`

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//


public enum EnumTest {
    INSTANCE;
	//此处是私有化的无参构造器,所以对其进行反射尝试获取他的私有化构造器实例化对象
    private EnumTest() {
    }

    public EnumTest getInstance() {
        return INSTANCE;
    }
}

测试代码:

public enum EnumTest {
    INSTANCE;
    public EnumTest getInstance(){
        return INSTANCE;
    }

}
class Test{
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Constructor<EnumTest> declaredConstructor = EnumTest.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        EnumTest enumTest = declaredConstructor.newInstance();
        System.out.println(enumTest);
    }
}

但是在运行过后会发现出现

Exception in thread "main" java.lang.NoSuchMethodException: com.single.EnumTest.<init>()
	at java.lang.Class.getConstructor0(Class.java:3082)
	at java.lang.Class.getDeclaredConstructor(Class.java:2178)
	at com.single.Test.main(EnumTest.java:15)

并没有出现预期的抛出不合法的参数异常,而是出现了一个NoSuchMethodException
所以对其字节码文件进行反编译后:

在这里插入图片描述
经过上述反编译过后,可以看出依旧是无参构造器,但是在枚举下其实是有参数的构造器

public enum EnumTest {
    INSTANCE;
    public EnumTest getInstance(){
        return INSTANCE;
    }

}
class Test{
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Constructor<EnumTest> declaredConstructor = EnumTest.class.getDeclaredConstructor(String.class,int.class);
        declaredConstructor.setAccessible(true);
        EnumTest enumTest = declaredConstructor.newInstance();
        System.out.println(enumTest);
    }
}
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
	at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
	at com.single.Test.main(EnumTest.java:17)
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值