《详解23种设计模式--单例模式 | CSDN创作打卡》

1、单例模式(Singleton)


新人小白,担心学个半桶水,灵机一动,边学边写博文吧,毕竟这里有好多大佬,希望有技术大牛不吝赐教,指点迷津,哈哈。同时也记录一下自己的学习历程,说不定可以给同样是小白的你一点启发、一丝灵感。一起共勉吧,为兴趣也为美好生活

本次学习基于Java语言

  • 【单例】一个类只允许创建一个实例
  • 【单例模式】是【创建者模式】的一种,创建者模式的主要关注点是 ‘如何创建对象’,核心思想是 ‘将对象的创建与使用分离’,让使用者无需关心对象创建的细节,从而降低系统的耦合度。

单例模式的结构:

  • 单例类:要求私有构造方法,创建本类实例,最后提供公共接口方便外界获取单例实例

单例模式有 2 种实现方式:

  • 饿汉式:类加载阶段就会创建单例类对象,如果只是加载类而没有使用该对象,会造成内存浪费,太不环保了~
  • 懒汉式:类加载阶段不会创建单例类对象,而是首次使用到该对象时才会创建

饿汉式–静态成员变量方式

public class Singleton {
    // 1、私有化改造方法,禁止外部直接创建对象
    private Singleton(){}
    // 2、创建单例对象并初始化提供给外界使用
    private static Singleton instance = new Singleton();
    // 3、提供对外访问接口,供使用者获取单例对象
    public static Singleton getInstance() {
        return instance;
    }
}

饿汉式–静态代码块方式
与静态变量方式差别不大,好处就是静态代码块中可以继续写代码进行判断,玩出新花样

public class Singleton {
    // 1、私有化改造方法,禁止外部直接创建对象
    private Singleton(){}
    // 2、创建单例对象提供给外界使用
    private static Singleton instance;

    static {
        instance = new Singleton();
    }
    // 3、提供对外访问接口,供使用者获取单例对象
    public static Singleton getInstance(){
        return instance;
    }
}

饿汉式–枚举方式
枚举方式实现的单例模式是线程安全的,是唯一不会被破坏的单例模式,不考虑内存浪费的情况下极力推荐使用

public enum Singleton {
    INSTANCE
}

懒汉式–双重检查锁方式(线程安全–推荐使用

public class Singleton {
    // 1、私有化改造方法,禁止外部直接创建对象
    private Singleton(){}
    // 2、创建单例对象提供给外界使用
    /* 
       JVM实例化对象时会进行指令重排序(可能会导致单例类实例化时出现空指针异常)
       使用volatile关键字可保证有序性 
    */
    private static volatile Singleton instance; 
    // 3、提供对外访问接口,供使用者获取单例对象
    public static Singleton getInstance(){
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

懒汉式–静态内部类方式(线程安全的方式–推荐使用
静态内部类单例模式中,单例实例由内部类创建,由于JVM在加载外部类的过程中,是不会加载静态内部类的,只有内部类的属性或方法被调用时才会加载,并初始化其静态属性。而静态属性由于被static修饰,可以保证只被实例化一次,并且严格保证了实例化顺序

public class Singleton {
    // 1、私有化改造方法,禁止外部直接创建对象
    private Singleton(){}
    private static class SingletonHolder {
        // 2、内部类中声明并初始化外部类实例
        private static final Singleton INSTANCE = new Singleton();
    }
    // 3、提供对外访问接口,供使用者获取单例对象
    public static Singleton getInstance(){
        return SingletonHolder.INSTANCE;
    }
}

被破坏的单例模式–反射破坏
通过 getInstanceByReflection() 方法体中反射机制,可以获取到多个单例类的实例,单例模式就被破坏了,解决方案就是通过 new构造器实例化对象时,进行逻辑判断

private static Singleton getInstanceByReflection() throws Exception{
    // 获取单例类字节码对象
    Class<Singleton> aClass = Singleton.class;
    // 获取单例类构造器对象
    Constructor<Singleton> c = aClass.getDeclaredConstructor();
    // 由于单例类构造器被private修饰,需要取消访问检查,俗称暴力反射
    c.setAccessible(true);
    return d.newInstance();
}

解决方法:在单例类构造器中进行判断
private static boolean flag = false;
private Singleton(){
    synchronized (Singleton.class) {
        if (flag) {// 第一次实例化对象时,flag为false,允许创建对象
            throw new RuntimeException("该类为单实例,不能创建多个对象");
        }
        flag = true;
    }
}

被破坏的单例模式–反序列化破坏
序列化单例类需要实现 Serializable 接口。
解决方案是:在单例类中定义 readResolve() 方法,通过解读对象输入流的 readObject() 方法的源码,可以发现,在执行反序列化时,会判断类中有没有定义 readResolve() 方法,如果有则自动调用该方法,并使 readObject() 方法直接返回 readResolve() 方法的返回值

private static Singleton readObjectFromFile() throws Exception{
    // 创建对象输入流
    ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\a.txt"));
    // 从指定文件读取对象进内存(反序列化)
    Singleton instance = (Singleton) ois.readObject();
    // 释放资源
    ois.close();
    return instance;
}
private static void writeObjectToFile() throws Exception{
    // 获取单例对象
    Singleton instance = Singleton.getInstance();
    // 创建对象输出流
    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\a.txt"));
    // 序列化对象到指定文件
    oos.writeObject(instance);
    // 释放资源
    oos.close();
}

解决方法:在单例类中定义readResolve()方法,原理是在反序列化时会自动调用该方法,直接返回该方法的返回值
public Object readResolve(){
    return SingletonHolder.INSTANCE;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值