单例模式之5种实现方式

(一) 单例模式概述

单例模式(Singleton Pattern): 是Java中最简单的设计模式之一, 它提供了一种创建对象的最佳方式. 这种模式涉及到一个单一的类, 该类负责创建自身的实例对象 且 创建的对象只存在一个实例. 这个类提供了一种访问其唯一对象的方式, 可以直接访问.

  • 单例类只能有一个实例
  • 单例类必须自己创建自己的唯一实例 且 无法通过new创建实例
  • 单例类必须给所有其他对象提供这一实例

单例模式的种类:

  • 饿汉式:
    • 饿汉式(静态常量)
    • 饿汉式(静态代码块)
  • 懒汉式
    • 懒汉式(线程不安全)
    • 懒汉式(线程安全, 同步方法)
    • 懒汉式(线程安全, 同步代码块)
  • 双重检查
  • 静态内部类
  • 枚举


(二) 单例模式实现的方式

1. 饿汉式

步骤:

  • 静态常量实例
  • 构造器私有化, 防止new创建实例
  • 类内部创建对象
  • 向外暴露一个静态的获取实例的公共方法
/**
 * 单例模式_饿汉式
 */
public class SingletonPattern_hungryType {

    public static void main(String[] args) {
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance1 == instance2); // 比较两个对象的内存地址: true
    }


}

class Singleton {
    // 静态常量实例
    // public final static Singleton instance= new Singleton();

    // 静态代码块 创建实例
    public final static Singleton instance;
    static {
        instance = new Singleton();
    }

    /**
     * 构造方法私有化
     */
    private Singleton() {}

    /**
     * 向外暴露一个静态的获取实例的公共方法
     *
     * @return
     */
    public static Singleton getInstance() {
        return instance;
    }
}

饿汉式的单例模式 优缺点:

  • 优点: 对象在类装载(类进入堆中)的时候就完成实例化, 避免了线程同步的问题
  • 缺点: 在类装载的时候就完成实例化, 没有达到Lazy Loading(懒加载)的效果. 如果从开始至终都没有使用该实例, 则会造成内存浪费 (导致类装载的原因有很多种, 如: 这个类中还有其他静态方法)



2. 懒汉式

懒汉式: 在该模式只有你需要对象时(调用getInstance方法), 才会实例化单例对象.

/**
 * 单例模式_懒汉式
 */
public class SingletonPattern_slackerType {

    public static void main(String[] args) {
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance1 == instance2); // 比较两个对象的内存地址: true
    }

}

class Singleton {
    // 静态成员属性
    public static Singleton instance;

    /**
     * 构造方法私有化
     */
    private Singleton() {
    }

    /**
     * 调用getInstance方法, 才实例化单例对象
     * 通过同步方法(synchronized)保证多线程下获取的对象是同一个实例
     *
     * @return
     */
    public synchronized static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

懒汉式的单例模式 优缺点:

  • 优点: 实现Lazy Loading(懒加载)的效果, 只有在真正使用时才会获取该对象的实例
  • 缺点: 为了保证多线程下获取的对象是同一个实例, 使用了同步方法(synchronized), 其他线程就必须等待整个方法执行完毕, 引起线程阻塞, 效率低下.



3. 双重检查

双重检查: 这种方式采用双锁机制, 两次执行 if (instance == null) 保证安全且在多线程情况下能保持高性能。

/**
 * 单例模式_双重检查
 */
public class SingletonPattern_doubleCheck {

    public static void main(String[] args) {
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance1 == instance2); // 比较两个对象的内存地址: true
        System.out.println(instance1.hashCode());
        System.out.println(instance2.hashCode());

    }

}

class Singleton {
    /**
     * volatile 修饰 静态成员变量
     *      被volatile关键字修饰的变量,如果值发生了变更,其他线程立马可见,避免出现脏读的现象(内存可见性)
     *      Java内存模型: 各个线程会将共享变量(instance)从主内存中拷贝到工作内存,然后执行引擎会基于工作内存中的数据进行操作处理
     *      volatile修饰的变量给java虚拟机特殊的约定,线程对volatile变量的修改 会同时修改 工作内存 和 主内存
     *          读取volatile修饰的变量, 会优先读取 主内存的变量, 如与工作内存值不一样会同步再返回
     */
    public static volatile Singleton instance;

    /**
     * 构造方法私有化
     */
    private Singleton() {
    }

    /**
     * 调用getInstance方法, 才实例化单例对象(懒加载)
     * 通过两次 if (instance == null) + 同步代码块, 解决线程安全问题且提高了效率 
     *
     * @return
     */
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
            instance = new Singleton();
        }
        return instance;
    }
}

双重检查的单例模式 优缺点:

  • 双重检查(Double-Check) 概念是多线程开发中经常使用到的, 在代码中, 我们进行了两次 if (instance == null) 检查, 保证线程安全
  • 实例代码只用执行了一次, 后面在次访问时, 判断 if (instance == null), 直接return实例化对象, 也避免反复执行方法同步
  • 线程安全, 延迟加载, 效率高



4. 静态内部类

采用静态内部类来创建对象实例, 静态内部类有已下特点:

  • 当一个类在类装载(初始化类, 会默认执行空的构造方法、静态代码块)时, 其 静态内部类 不会被装载
  • 只有外部类使用到 静态内部类 时, 静态内部类 才会被装载
/**
 * 单例模式_静态内部类
 */
public class SingletonPattern_staticInnerClass {

    public static void main(String[] args) {
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance1 == instance2); // 比较两个对象的内存地址: true
        System.out.println(instance1.hashCode());
        System.out.println(instance2.hashCode());
    }
}

class Singleton {

    /**
     * 构造方法私有化
     */
    private Singleton() {}

    /**
     * 通过静态内部类获取对象实例
     *
     * @return
     */
    public static Singleton getInstance() {
        return SingletonInstance.SINGLETON;
    }

    /**
     * 静态内部类SingletonInstance: 类中存在 Singleton的静态常量 且 实例化对象
     */
    private static class SingletonInstance {
        private static final Singleton SINGLETON = new Singleton();

        static {
            System.out.println("静态内部类装载(初始化)");
        }
    }
}

双重检查的单例模式 优缺点:

  • 这种方式采用了类装载的机制来保证只初始化一个实例

  • 静态内部类在 Singleton类装载时并不会立即实例化, 而是在需要实例化时, 调用getInstance方法, 才会装载SingletonInstance类, 从而完成Singleton的实例化

  • 类的静态属性只会在第一次加载类的时候初始化, 因此是 JVM 保证了线程的安全性, 在类进行初始化时, 别的线程是无法进入的

  • 优点: 避免了线程不安全, 利用静态内部类特点实现延迟加载, 效率高



5. 枚举
/**
 * 单例模式_枚举
 */
public class SingletonPattern_enum {

    public static void main(String[] args) {
        SingletonEnum instance1 = SingletonEnum.INSTANCE; // 调用SingletonEnum枚举类的无参构造方法
        SingletonEnum instance2 = SingletonEnum.INSTANCE;
        System.out.println(instance1 == instance2); // 比较两个对象的内存地址: true
        System.out.println(instance1.hashCode());
        System.out.println(instance2.hashCode());
    }
}

/**
 * SingletonEnum枚举: 只有一个 INSTANCE 的实例
 */
enum SingletonEnum {
    INSTANCE;
}

借助JDK1.5中添加的枚举来实现单例模式, 不仅能避免多线程同步问题, 而且还能防止反序列化重新创建对象(最简单实现的单例模式)



(三) 总结

单例模式, 就是采取一定的方法保证在整个的软件系统中, 对某个类只能存在一个对象实例, 并且该类只提供一个取得对象实例的静态方法.

  • 单例模式保证了系统内存中该类只存在一个实例对象, 节省了系统资源, 对于一些需要频繁创建销毁的对象, 使用单例模式可以提高系统性能, 如: 工具类对象、频繁访问数据库或文件的对象(数据源、session工场等)
  • 当想实例化一个单例对象的时候, 必须要记住使用相应的获取对象的静态方法, 而不是使用new的方式创建
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
单例模式是一常用的设计模式,它保证某个类只有一个实例存在,并提供一个全局访问点。在 Python 中,单例模式实现方式有以下几种: 1. 使用模块:Python 的模块是天然的单例模式,因为模块在第一次导入时会被缓存,之后的导入会直接使用缓存中的模块对象。因此,我们可以将需要实现单例模式的类定义在一个模块中,然后在其他模块中导入这个模块即可。 2. 使用 __new__ 方法:Python 中的每个类都有一个特殊的 __new__ 方法,它会在实例化对象时被调用。我们可以重写 __new__ 方法,在第一次实例化对象时创建一个实例,并将其缓存起来,之后的实例化直接返回缓存的实例即可。 3. 使用装饰器:我们可以编写一个装饰器,将需要实现单例模式的类装饰起来,使其只能实例化一次。具体实现可以利用闭包来保存单例对象,每次实例化时判断是否已经实例化过,如果是则直接返回单例对象。 4. 使用元类:元类是用于创建类的类,我们可以编写一个元类,重写其 __call__ 方法,在创建实例时判断是否已经存在实例,如果存在则直接返回单例对象。 对于这些实现方式,还有一些优化技巧可以提升其效率和可用性,例如: 1. 线程安全:如果多个线程同时调用单例对象,可能会导致创建多个实例的情况,因此我们需要加锁来保证线程安全。 2. 序列化和反序列化:如果单例对象需要进行序列化和反序列化操作,我们需要实现 __getstate__ 和 __setstate__ 方法,以便正确地保存和加载单例对象。 3. 继承和子类化:如果我们需要从单例类派生子类,可能会破坏单例模式,因此我们需要重写 __new__ 方法,以便在创建子类对象时也返回单例对象。 综上所述,单例模式是一常用的设计模式,在 Python 中有多实现方式和优化技巧可供选择。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值