设计模式之单例模式(Java)

单例模式实现方式:懒汉式、饿汉式、双重检查、枚举、静态内部类;

懒汉式:

复制代码
/**
 * 懒汉式单例模式
 * @author: 小手WA凉
 * @create: 2024-07-06
 */
public class LazySingleton implements Serializable {
    private static LazySingleton lazySingleton=null;
    private LazySingleton(){

    }
    //特点:第一次调用才初始化,避免内存浪费。
    public synchronized static LazySingleton getInstance(){
        if(lazySingleton==null){
            lazySingleton=new LazySingleton();
        }
        return lazySingleton;
    }
}

饿汉式:

java 复制代码
/**饿汉式单例模式
 * @author: 小手WA凉
 * @create: 2024-07-06
 */
public class HungrySingleton {
    static final HungrySingleton hungrySingleton=new HungrySingleton();
    private HungrySingleton(){

    }
    //特点:类加载时就初始化,线程安全
    public static HungrySingleton getInstance(){
        return hungrySingleton;
    }
}

双重检查:

和懒汉式的区别就是,锁的范围减小了,防止多线程场景下创建多个实例,所以又加了一层判断。

java 复制代码
/**双重检查单例模式
 * @author: 小手WA凉
 * @create: 2024-07-06
 */
public class DoubleCheckSingletion {
    private static DoubleCheckSingletion doubleCheckSingletion=null;
    private DoubleCheckSingletion(){

    }
    //特点:安全且在多线程情况下能保持高性能
    public static DoubleCheckSingletion getInstance(){
        if(doubleCheckSingletion==null){
            synchronized (DoubleCheckSingletion.class){
                if(doubleCheckSingletion==null){
                    doubleCheckSingletion=new DoubleCheckSingletion();
                }
            }
        }
        return doubleCheckSingletion;
    }
}

枚举:

java 复制代码
/**枚举形单例模式
 * @author: 小手WA凉
 * @create: 2024-07-06
 */
public enum EnumSingleton {
    INSTANCE;
    //特点:自动支持序列化机制,绝对防止多次实例化
    public static EnumSingleton getInstance(){
        return INSTANCE;
    }
}

静态内部类:

java 复制代码
/**静态内部类单例模式
 * @author: 小手WA凉
 * @create: 2024-07-06
 */
public class StaticInnerClassSingleton {
    private static class InnerClass{
        private static StaticInnerClassSingleton staticInnerClassSingleton=new StaticInnerClassSingleton();
    }
    private StaticInnerClassSingleton(){

    }

    public static StaticInnerClassSingleton getInstance(){
        return InnerClass.staticInnerClassSingleton;
    }
}

序列化或反射破坏单列模式:

通过序列化反序列化或反射可以破坏除枚举以外的其它实现方式:

java 复制代码
LazySingleton instance = LazySingleton.getInstance();
        //序列化破坏单例模式
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singletion"));
        oos.writeObject(instance);
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("singletion"));
        LazySingleton instance2 = (LazySingleton) ois.readObject();
        System.out.println(instance);
        System.out.println(instance2);
        System.out.println(instance2==instance);

运行:

解决反序列化破坏单列模式

解决方法只需要在单例类里加上一个readResolve()方法即可,原因就是在反序列化的过程中,会检测readResolve()方法是否存在,如果存在的话就会反射调用readResolve()这个方法。

java 复制代码
private Object readResolve(){
        return lazySingleton;
    }

使用枚举单例模式天然就可以防止序列化破坏单例模式:

java 复制代码
 EnumSingleton instance = EnumSingleton.getInstance();
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singletion"));
        oos.writeObject(instance);
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("singletion"));
        EnumSingleton instance2 = (EnumSingleton) ois.readObject();
        System.out.println(instance);
        System.out.println(instance2);
        System.out.println(instance==instance2);

运行:

为什么?

  • 枚举类的序列化保证单例: Java枚举类型在序列化和反序列化时会被特殊处理。在序列化的过程中,只是将枚举对象的名字(即枚举常量的名字)写入到序列化文件中;在反序列化的过程中,通过名字来获取枚举对象。这保证了在反序列化过程中无论如何都只会得到枚举中定义的枚举常量,而不会重新创建新的对象。因此,枚举类型的单例模式在反序列化过程中也能保持单例的状态,不会被破坏。

  • 禁止反射创建多个实例 : 枚举类型的单例模式天然地禁止了通过反射机制来创建多个实例。枚举类型的构造器是私有的,并且编译器会确保枚举常量只能被实例化一次。如果尝试使用反射来调用枚举类型的私有构造器来创建新的实例,会抛出IllegalArgumentException异常,从而保证了单例的唯一性。

反射破坏单例模式:

java 复制代码
/**反射破坏单例模式
 * @author: 小手WA凉
 * @create: 2024-07-06
 */
public class BrokenSingletonTest2 {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        LazySingleton instance = LazySingleton.getInstance();
        Class<LazySingleton> lazySingletonClass = LazySingleton.class;
        Constructor<LazySingleton> constructor = lazySingletonClass.getDeclaredConstructor();
        constructor.setAccessible(true);
        LazySingleton refInstance = constructor.newInstance();
        System.out.println(instance);
        System.out.println(refInstance);
        System.out.println(instance==refInstance);
    }
}

运行:

枚举天然也可以防止反射破坏单例模式:

java 复制代码
        EnumSingleton instance = EnumSingleton.getInstance();
        Class<EnumSingleton> singletonClass = EnumSingleton.class;
        Constructor<EnumSingleton> constructor = singletonClass.getDeclaredConstructor(String.class,int.class);
        constructor.setAccessible(true);
        EnumSingleton refInstance = constructor.newInstance();
        System.out.println(instance);
        System.out.println(refInstance);
        System.out.println(instance==refInstance);

运行:

这也是为什么枚举是实现单例模式最好的方式原因之一。

相关推荐
captain3764 小时前
MyBatis(入门5)其他查询操作
java·数据库·spring boot·mybatis
Novproxy_Z5 小时前
商用代理避坑:如何识别商家拿机房 IP 冒充住宅 IP
java·网络·tcp/ip
文丑颜不良啊6 小时前
LeetCode1481~1500题目解析
java·leetcode
2601_962885726 小时前
quotes 字段全解:涨跌幅、振幅、换手率的口径与单位
java·前端·spring
前端精髓6 小时前
调用openrouter的免费API模型
java·前端·javascript
步行cgn6 小时前
Spring MVC 拦截器 Interceptor 详解:从执行流程到实战应用
java·spring·mvc
文丑颜不良啊6 小时前
LeetCode1381~1400题目解析
java·leetcode
开开心心_Every7 小时前
快速隐藏程序工具自定义快捷键一键切换
java·开发语言·前端·spring·智能手机·word·intellij-idea
萧瑟余晖7 小时前
Spring Batch核心模型与快速上手详解
java·spring·batch
缓慢更新8 小时前
AI 辅助生成单测:从接口定义到用例的流水线
java·服务器·前端·网络·数据库·人工智能