设计模式之单例模式(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);

运行:

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

相关推荐
_oP_i33 分钟前
Pinpoint 是一个开源的分布式追踪系统
java·分布式·开源
mmsx36 分钟前
android sqlite 数据库简单封装示例(java)
android·java·数据库
武子康1 小时前
大数据-258 离线数仓 - Griffin架构 配置安装 Livy 架构设计 解压配置 Hadoop Hive
java·大数据·数据仓库·hive·hadoop·架构
豪宇刘2 小时前
MyBatis的面试题以及详细解答二
java·servlet·tomcat
秋恬意2 小时前
Mybatis能执行一对一、一对多的关联查询吗?都有哪些实现方式,以及它们之间的区别
java·数据库·mybatis
FF在路上3 小时前
Knife4j调试实体类传参扁平化模式修改:default-flat-param-object: true
java·开发语言
真的很上进3 小时前
如何借助 Babel+TS+ESLint 构建现代 JS 工程环境?
java·前端·javascript·css·react.js·vue·html
众拾达人4 小时前
Android自动化测试实战 Java篇 主流工具 框架 脚本
android·java·开发语言
皓木.4 小时前
Mybatis-Plus
java·开发语言
不良人天码星4 小时前
lombok插件不生效
java·开发语言·intellij-idea