设计模式

单例模式是一种创建型设计模式, 让你能够保证一个类只有一个实例, 并提供一个访问该实例的全局节点

保证一个类只有一个实例为什么会有人想要控制一个类所拥有的实例数量最常见的原因是控制某些共享资源例如数据库或文件的访问权限它的运作方式是这样的如果你创建了一个对象同时过一会儿后你决定再创建一个新对象此时你会获得之前已创建的对象而不是一个新对象注意普通构造函数无法实现上述行为因为构造函数的设计决定了它必须总是返回一个新对象

为该实例提供一个全局访问节点还记得你好吧其实是我自己用过的那些存储重要对象的全局变量吗它们在使用上十分方便但同时也非常不安全因为任何代码都有可能覆盖掉那些变量的内容从而引发程序崩溃和全局变量一样单例模式也允许在程序的任何地方访问特定对象但是它可以保护该实例不被其他代码覆盖还有一点你不会希望解决同一个问题的代码分散在程序各处的因此更好的方式是将其放在同一个类中特别是当其他代码已经依赖这个类时更应该如此

实现步骤:

所有单例的实现都包含以下两个相同的步骤

  • 将默认构造函数设为私有防止其他对象使用单例类的 new运算符

  • 新建一个静态构建方法作为构造函数该函数会偷偷调用私有构造函数来创建对象并将其保存在一个静态成员变量中此后所有对于该函数的调用都将返回这一缓存对象

如果你的代码能够访问单例类那它就能调用单例类的静态方法无论何时调用该方法它总是会返回相同的对象

伪代码
public final class Singleton {
    private static Singleton instance;
    public String value;

    private Singleton(String value) {
        this.value = value;
    }

    public static Singleton getInstance(String value) {
        if (instance == null) {
            instance = new Singleton(value);
        }
        return instance;
    }
}
单例模式优缺点
优点:
  1. 你可以保证一个类只有一个实例

  2. 你获得了一个指向该实例的全局访问节点

  3. 仅在首次请求单例对象时对其进行初始化

缺点:
  1. 违反了单一职责原则, 该模式同时解决了两个问题

  2. 单例模式可能掩盖不良设计, 比如程序各组件之间相互了解过多等

  3. 该模式在多线程环境下需要进行特殊处理, 避免多个线程多次创建单例对象

  4. 单例的客户端代码单元测试可能会比较困难, 因为许多测试框架以基于继承的方式创建模拟对象。 由于单例类的构造函数是私有的, 而且绝大部分语言无法重写静态方法, 所以你需要想出仔细考虑模拟单例的方法。 要么干脆不编写测试代码, 或者不使用单例模式

只保证创建一个对象,为什么还要避免多线程创建对象
  1. 重复创建对象:多个线程同时通过单例模式创建对象时,可能会导致多次实例化,破坏了单例模式的初衷。

  2. 状态不一致:如果单例对象的创建过程中涉及到一些状态的设置或初始化操作,多个线程同时进行创建可能会导致状态不一致,使得对象处于不可预测的状态。

  3. 资源竞争:如果单例对象的创建涉及到一些共享资源或者文件操作等,多个线程同时创建可能会导致资源竞争和冲突,进而造成程序崩溃或数据损坏等问题

对于第一点可以考虑以下情况:

  1. 线程 A 和线程 B 同时检查到实例对象不存在。

  2. 线程 A 先获取到锁,开始创建实例对象。

  3. 线程 A 创建完实例对象后释放锁。

  4. 线程 B 获取到锁,然后也创建实例对象。

  5. 这样就导致了两个实例对象的创建

这就是多线程环境下可能出现的问题。为了解决这个问题,需要在创建实例对象的过程中引入同步机制,确保只有一个线程可以执行创建操作。常用的方法包括使用双重检查锁、静态内部类等,这些方法可以有效地保证在多线程环境下只创建一个实例对象

单例模式在java中的应用场景
Spring Bean管理

在Spring框架中,当我们声明一个Bean时,默认情况下是单例的。这意味着Spring容器会管理Bean的生命周期,并确保在整个应用程序中只有一个实例

@Component
public class MySingletonBean {
    // Class definition
}
线程池

在多线程环境下,线程池是一种常见的单例对象。它负责管理可用的线程,并提供给需要执行任务的线程

ExecutorService executorService=Executors.newFixedThreadPool(10);
日志对象

在Java应用中,通常会使用单例模式来管理日志记录器对象,以确保在整个应用程序中只有一个日志实例

public class Logger {
    private static Logger instance;

    private Logger() {
        // private constructor to prevent instantiation
    }

    public static Logger getInstance() {
        if (instance == null) {
            instance = new Logger();
        }
        return instance;
    }

    public void log(String message) {
        // log message
    }
}
数据库连接池

在使用数据库时,可以使用单例模式来管理数据库连接池,以确保在应用程序中只有一个连接池实例,避免资源浪费和性能问题

public class DatabaseConnectionPool {
    private static DatabaseConnectionPool instance;

    private DatabaseConnectionPool() {
        // private constructor to prevent instantiation
    }

    public static DatabaseConnectionPool getInstance() {
        if (instance == null) {
            instance = new DatabaseConnectionPool();
        }
        return instance;
    }

    // Other methods for managing database connections
}