当前位置: 首页 > article >正文

【只生一个好 - 单例设计模式(Singleton Pattern)】

单例设计模式

  • 单例设计模式(Singleton Pattern)
    • talk is cheap, show you my code
      • 饿汉式
      • 懒汉式
      • 双重检查锁定
      • 静态内部类
      • 枚举
    • 总结

单例设计模式(Singleton Pattern)

单例设计模式(Singleton Pattern)是面向对象编程中的一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点来访问这个唯一的实例。这种模式在需要控制资源的访问或配置时非常有用,例如数据库连接池、线程池、缓存等场景。

太抽象了
来我们换个说法,当一些资源比较重的时候,我们不希望创建那么多的实例。举个现实的例子,我们有一大堆地方法院;但是最高法院只有一个。为什么? 因为当所有的事情有分歧的时候,一层层上报,总是需要有一个部门有最终决策权。

talk is cheap, show you my code

饿汉式

饿汉式是在类加载时就创建好实例,这种方式简单直接,但缺点是即使实例从未被使用过,也会占用内存资源。

public class Singleton {

    private static Singleton singleton = new Singleton();

    private Singleton() {
    }

    public static Singleton getInstance() {
        return singleton;
    }
}

我们通过将Singleton设置为private,使得外部没有办法调用构造方法新建实例;又利用类加载机制,我们将Singleton设置为静态变量,这样的话,类加载的时候,就可以新建一个实例;最后开放一个静态方法getInstance()返回实例。

懒汉式

懒汉式是在第一次调用 getInstance() 方法时才创建实例,这样可以节省资源,但是如果多线程环境下未加锁可能会导致多个实例被创建。

public class Singleton1 {
    private static Singleton1 singleton = null;

    private Singleton1() {
    }

    public static Singleton1 getInstance() {
        if (singleton == null) {
            singleton = new Singleton1();
        }
        return singleton;
    }
}

我们通过将Singleton设置为private,使得外部没有办法调用构造方法新建实例;然后调用getInstance()的时候触发新建实例的动作,返回实例,但是这个代码在多线程下可能会创建出多个实例。

双重检查锁定

上面的懒汉式单例写入有多线程的安全问题,为了保证线程安全同时避免不必要的同步开销,可以采用双重检查锁定的方式。

public class Singleton1 {
    private volatile static Singleton1 singleton = null;

    private Singleton1() {
    }

    public static Singleton1 getInstance() {
        if (singleton == null) {
            synchronized (Singleton1.class) {
                if (singleton == null) {
                    singleton = new Singleton1();
                }
            }
        }
        return singleton;
    }
}

synchronized这个关键字用来加锁,但是仅仅通过synchronized也不能保证只产生一个实例,为什么呢?
因为第一个 if (singleton == null)后面通过synchronized拿锁,然后有一个线程会拿到锁,另一些在等待锁,如果不写第二个if判断的话,导致第一个拿到锁的执行了创建实例的操作,释放锁。而等待锁的线程就可以拿到锁,导致又创建了新的实例,所以我们要写两个if判断。所以这种创建单例的写法又叫做双重检查锁定。

静态内部类

public class Singleton {
    private Singleton() {}

    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

利用 Java 的类加载机制,在第一次调用 getInstance() 方法时才会加载静态内部类,从而实现延迟初始化和线程安全。换言之,这种的线程安全是通过类加载机制保证的,我们就不需要再写线程安全控制了。
另外补充private static final Singleton INSTANCE = new Singleton();这个看起来像是饿汉式,但是实际上只有调用getInstance的时候才会创建对象,因为SingletonHolder是在它自己被调用的时候才会触发类加载。所以从创建时机上看,这种是懒汉式一样的。

枚举

枚举天生就是线程安全的,并且可以防止反序列化攻击,因此它是一种非常简洁且推荐的方式来实现单例模式。

public enum Singleton {
    INSTANCE;

    // 单例对象的成员变量
    private String data;

    // 私有构造函数
    private Singleton() {
        this.data = "Initial Data";
    }

    // 获取数据的方法
    public String getData() {
        return data;
    }

    // 设置数据的方法
    public void setData(String data) {
        this.data = data;
    }

    // 其他方法
    public void doSomething() {
        System.out.println("Doing something with data: " + data);
    }

    // 主方法用于测试
    public static void main(String[] args) {
        // 获取单例实例
        Singleton singleton = Singleton.INSTANCE;

        // 使用单例实例的方法
        System.out.println(singleton.getData()); // 输出: Initial Data
        singleton.setData("New Data");
        System.out.println(singleton.getData()); // 输出: New Data
        singleton.doSomething(); // 输出: Doing something with data: New Data
    }
}

总结

单例模式特点:

  1. 单例模式的优点
  • 唯一性:确保了类在整个应用程序中只有一个实例。
  • 控制资源:对于需要严格控制资源使用的场合非常适合。
  • 延迟加载:可以通过某些实现方式(如懒汉式、静态内部类)实现延迟初始化,节省资源。
  • 线程安全:通过适当的实现方式可以保证在多线程环境下的安全性。
  1. 单例模式的缺点
  • 难以测试:单例模式引入了全局状态,这可能使得单元测试变得困难。
  • 隐藏依赖关系:因为单例模式通常通过静态方法提供实例,所以依赖关系往往被隐藏起来,不利于依赖注入和代码维护。
  • 不适用于分布式系统:在一个分布式的环境中,单例模式无法保证跨进程或跨机器的唯一性。

单例模式应用场景

  • 配置管理:如读取配置文件或环境变量。
  • 日志记录器:整个应用共享同一个日志记录器实例。
  • 工厂类:如工厂模式中的工厂类,确保所有客户端都使用同一个工厂来创建对象。
  • 数据库连接池:控制对数据库连接的访问,确保高效利用有限的连接资源。

单例模式是一个简单而强大的设计模式,广泛应用于各种需要确保唯一实例的场景。Spring 中的Bean默认就是单例模式的。


http://www.kler.cn/a/455227.html

相关文章:

  • C++第五六单元测试
  • VUE3+VITE简单的跨域代理配置
  • 详细对比JS中XMLHttpRequest和fetch的使用
  • 【开发问题记录】执行 git cz 报require() of ES Module…… 错误
  • 内置ALC的前置放大器D2538A/D3308
  • 一个服务器可以搭建几个网站?搭建一个网站的流程介绍
  • Rocky DEM tutorial7_Conical Dryer_锥形干燥器
  • Linux | 零基础Ubuntu卸载MySQL Server 零痕迹
  • 支持selenium的chrome driver更新到131.0.6778.204
  • 吴恩达深度学习-第一周作业-题目
  • 结构方程模型【SEM】:非线性、非正态、交互作用及分类变量分析
  • 【人工智能学习】线性回归模型使用Python实现简单的线性回归
  • 我们来学mysql -- 区分大写
  • 王佩丰24节Excel学习笔记——第十八讲:Lookup和数组
  • C++ 的IO流(标准IO流 和文件IO流)
  • css 样式隐形
  • python如何使用RSA加密,避免明文密码
  • Centos7.9自动封禁IP
  • 基于 Python Django 的农产品销售系统的研究与实现
  • IEC103 转 ModbusTCP 网关(三格电子)