单例模式分析

本文详细探讨了Java中的单例模式,包括饿汉模式、懒汉模式、DCL双重检查锁模式以及静态内部类和枚举方法的实现。同时,文章指出了反射可能对单例模式的破坏,并提供了相应的解决方案,如在构造器中加入检查以防止反射破坏。最后,通过枚举实现单例模式被证明是线程安全且难以被反射破坏的。

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


单例模式

特点:枚举可以避免单例模式被破坏,构造器私有

饿汉模式

在类装载的时候就完成实例化,上来就把对象创建,用的时候直接使用,但是启动慢,就算不用也创建了,浪费内存。避免了线程同步问题。

public class Hungry {
    //饿汉模式
    private Hungry(){
    }
    private static final Hungry HUNGRY = new Hungry();
    public Hungry getInstance(){
        return HUNGRY;
    }
}

懒汉模式

用的时候才会创建

public class Lazy {
    //懒汉模式,但是这种方法只适用在单线程,并发不可以
    private Lazy(){
    }
    private static Lazy lazy;
    public Lazy getInstance(){
        if(lazy==null){
            lazy = new Lazy();
        }
        return lazy;
    }
}

DCL双重检测锁懒汉模式,解决并发问题

synchronized保证原子性,volatile保证有序性

public class Lazy {
    private Lazy(){
    }
    private volatile static Lazy lazy;
    //第一层保证线程安全,提高效率
    public static Lazy getInstance(){
        if(lazy==null){
            synchronized (Lazy.class){
                if(lazy==null){
                    lazy = new Lazy();
                    //new包含3步
                    /*
                    1.分配内存空间
                    2.执行构造方法,初始化对象
                    3.把这个对象指向这个空间
                    */
                }
            }
        }
        return lazy;
    }
}

new的机器码不止一步。也就是不是原子性操作,由于指令的重排序,可能在未完成实例化的时候,对象已经不是null,就是顺序为132,这时候另一个线程就有可能得到未完成实例化的对象。

所以需要在private static Lazy lazy;加上volatile关键字,就会保证指令不会重排了

静态内部类法

public class Holder {
    private Holder(){
    }
        private static class InnerClass{
            private static final Holder HOLDER = new Holder();
        }
    public Holder getInstance(){
        return InnerClass.HOLDER;
    }
}

但是这些都是不安全的,可以通过反射来进行单例的破坏

public class Lazy {
    private Lazy() {
    }

    private volatile static Lazy lazy;

    public static Lazy getInstance() {
        if (lazy == null) {
            synchronized (Lazy.class) {
                if (lazy == null) {
                    lazy = new Lazy();
                }
            }
        }
        return lazy;
    }
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Lazy lazy = Lazy.getInstance();
        Constructor<Lazy> declaredConstructor = Lazy.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        Lazy lazy1 = declaredConstructor.newInstance();
        System.out.println(lazy);
        System.out.println(lazy1);
    }
}
/*结果
com.jin.test.Lazy@5cad8086
com.jin.test.Lazy@6e0be858
明显看出是两个对象
*/

反射破坏的解决方案

由于是使用反射进行无参构造来创建对象,所以修改无参构造

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

    public static Lazy getInstance() {
        if (lazy == null) {
            synchronized (Lazy.class) {
                if (lazy == null) {
                    lazy = new Lazy();
                }
            }
        }
        return lazy;
    }
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Lazy lazy = Lazy.getInstance();
        Constructor<Lazy> declaredConstructor = Lazy.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        Lazy lazy1 = declaredConstructor.newInstance();
        System.out.println(lazy);
        System.out.println(lazy1);
    }
}

但是会出现另一种破坏方式,当不使用单例模式来进行创建对象,只使用反射来创建对象还是会出现两个对象的问题

public class Lazy {
    private Lazy() {
        synchronized (Lazy.class){
            if(lazy!=null){
                throw new RuntimeException("不要试图使用反射破坏异常");
            }
        }
    }

    private volatile static Lazy lazy;

    public static Lazy getInstance() {
        if (lazy == null) {
            synchronized (Lazy.class) {
                if (lazy == null) {
                    lazy = new Lazy();
                }
            }
        }
        return lazy;
    }
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//        Lazy lazy = Lazy.getInstance();
        Constructor<Lazy> declaredConstructor = Lazy.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        Lazy lazy1 = declaredConstructor.newInstance();
        Lazy lazy2 = declaredConstructor.newInstance();
        System.out.println(lazy1);
        System.out.println(lazy2);
    }
}
/*结果
com.jin.test.Lazy@5cad8086
com.jin.test.Lazy@6e0be858
*/

这里使用一种红绿灯法,来重新再无参构造中判断,标志位的字段可以加密,什么都可以,除了反编译,无法找到该字段

public class Lazy {
    private static boolean fdskfjskd=false;
    private Lazy() {
        synchronized (Lazy.class){
            if(fdskfjskd==false){
                fdskfjskd=true;
            }
            else
                throw new RuntimeException("不要试图使用反射破坏异常");  
        }
    }
    private volatile static Lazy lazy;
    
    public static Lazy getInstance() {
        if (lazy == null) {
            synchronized (Lazy.class) {
                if (lazy == null) {
                    lazy = new Lazy();
                }
            }
        }
        return lazy;
    }
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//        Lazy lazy = Lazy.getInstance();
        Constructor<Lazy> declaredConstructor = Lazy.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        Lazy lazy1 = declaredConstructor.newInstance();
        Lazy lazy2 = declaredConstructor.newInstance();
        System.out.println(lazy1);
        System.out.println(lazy2);
    }
}

但是若该字段被破译,仍旧可以被用反射破坏

public class Lazy {
    private static boolean fdskfjskd=false;
    private Lazy() {
        synchronized (Lazy.class){
            if(fdskfjskd==false){
                fdskfjskd=true;
            }
            else
                throw new RuntimeException("不要试图使用反射破坏异常");

        }
    }

    private volatile static Lazy lazy;

    public static Lazy getInstance() {
        if (lazy == null) {
            synchronized (Lazy.class) {
                if (lazy == null) {
                    lazy = new Lazy();
                }
            }
        }
        return lazy;
    }
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
//        Lazy lazy = Lazy.getInstance();
        Constructor<Lazy> declaredConstructor = Lazy.class.getDeclaredConstructor(null);
        //得到该字段
        Field fdskfjskd = Lazy.class.getDeclaredField("fdskfjskd");
        declaredConstructor.setAccessible(true);
        Lazy lazy1 = declaredConstructor.newInstance();
        fdskfjskd.set(lazy1,false);
        Lazy lazy2 = declaredConstructor.newInstance();
        System.out.println(lazy1);
        System.out.println(lazy2);
    }
}
/*
结果
com.jin.test.Lazy@6e0be858
com.jin.test.Lazy@61bbe9ba
仍旧被破坏
*/

通过阅读newInstance()的源码,得出不能使用反射破坏枚举,jdk1.5出来的

在这里插入图片描述

枚举本身也是一个class类

枚举方法

public enum EnumSingle {
    INSTANCE;
    public EnumSingle getInstance(){
        return INSTANCE;
    }
}
class test{
public static void main(String[]args) throws Exception {
    EnumSingle instance = EnumSingle.INSTANCE.getInstance();
    Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(null);
    declaredConstructor.setAccessible(true);
    EnumSingle enumSingle = declaredConstructor.newInstance();
    System.out.println(instance);
    System.out.println(enumSingle);

 }
}
/*
结果
Exception in thread "main" java.lang.NoSuchMethodException:
 com.jin.test.EnumSingle.<init>()
*/

用反射创建对象后,并没有出现源码的Cannot reflectively create enum objects

而是Exception in thread "main" java.lang.NoSuchMethodException:

通过反编译软件得出枚举类型的代码

public final class EnumSingle extends Enum
{
    public static EnumSingle[] values()
    {
        return (EnumSingle[])$VALUES.clone();
    }
    public static EnumSingle valueOf(String name)
    {
        return (EnumSingle)Enum.valueOf(EnumSingle, name);
    }
    private EnumSingle(String s, int i)
    {
        super(s, i);
    }
    public EnumSingle getInstance()
    {
        return INSTANCE;
    }
    public static final EnumSingle INSTANCE;
    private static final EnumSingle $VALUES[];
    static 
    {
        INSTANCE = new EnumSingle("INSTANCE", 0);
        $VALUES = (new EnumSingle[] {
            INSTANCE
        });
    }
}

可以看出反编译后的代码,并没有无参构造,而是有参构造,修改代码

public enum EnumSingle {
    INSTANCE;
    public EnumSingle getInstance(){
        return INSTANCE;
    }
}
class test{
public static void main(String[]args) throws Exception {
    EnumSingle instance = EnumSingle.INSTANCE.getInstance();
    Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
    declaredConstructor.setAccessible(true);
    EnumSingle enumSingle = declaredConstructor.newInstance();
    System.out.println(instance);
    System.out.println(enumSingle);

  }
}
/*
结果
Exception in thread "main" java.lang.IllegalArgumentException: 
Cannot reflectively create enum objects
*/

得到预想的结果

通过枚举反编译代码看出,枚举类型是通过静态代码块实现对象的创建,是安全的,但是静态代码块可能会浪费内存(待议)。

我们可以通过EasySingleton.INSTANCE.工具方法() 的方式来访问实例,这比调用getInstance()方法简单多了。创建枚举默认就是线程安全的,所以不需要担心double checked locking,而且还能防止反序列化和拷贝导致重新创建新的对象。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值