设计模式-单例模式

248 阅读5分钟

一、什么是单例模式?

单例模式是一种对象创建型模式。确保一个类只有一个实例,并提供一个全局访问点来访问这个唯一实例。包含三个要点:

  • 某个只能有一个实例
  • 该类必须自行创建这个实例
  • 该类必须自行向整个系统提供这个实例

相对应的,实现单例模式也需要三个必要条件:

  • 构造函数私有
  • 存储唯一实例的静态变量
  • 访问唯一实例的公有静态方法

使用单例模式时需要考虑的三个问题:

  • 创建单例对象时,是否线程安全
  • 创建单例对象时,是否延迟加载
  • 获取单例对象时,是否需要加锁(影响性能)

二、单例模式在JAVA中的实现方式

对于上面提到的使用单例模式时考虑的三个问题,单例模式有了多种实现方式,下面介绍几种常用的实现。

  • 饿汉式单例:最简单的单例模式实现,饿汉式没有考虑延迟加载的问题,在类初始化的时候即创建了一个唯一实例。
public class EagerSingleton{
    # 存储唯一实例的静态变量
    private static final EagerSingleton instance = new Singleton();
    # 构造函数私有
    private EagerSingleton(){}
    # 访问唯一实例的公有静态方法
    public static Singleton getInstance(){
        return instance;
    }
}
  • 懒汉式单例:与饿汉式不同的是,懒汉式并不是在类初始化的时候就创建了实例,而是在需要用到实例的时候再创建,这样做实现了延迟加载,但是在多线程访问的时候,创建实例可能会引发线程安全问题,所以可以在方法前添加synchronized关键字应对。
public class LazySingleton{
    private static LazySingleton instance = null;
    private LazySingleton(){}
    
    public static synchronized LazySingleton getInstance(){
        //如果实例为空则创建实例,否则直接返回实例
        if(instance == null){
            instance = new LazySingleton();
        }
        return instance;
    }
    
    
}

上述代码虽然通过添加synchronized解决了线程安全问题,但是在多线程高并发访问环境中将会导致系统性能大大降低,下面考虑对其进行优化,可以缩小synchronized的范围,只需要对instance = new LazySingleton()这行代码进行锁定即可。

public class LazySingleton{
    private static LazySingleton instance = null;
    private LazySingleton(){}
    
    public static LazySingleton getInstance(){
        //如果实例为空则创建实例,否则直接返回实例
        if(instance == null){
            synchronized(LazySingleton.class){
                instance = new LazySingleton();
            }
        }
        return instance;
    }
}

这样是否万事大吉了呢?并没有!考虑这种情况,线程A和B同时通过instance == null的判断,此时线程A进行实例化,线程B进行等待,当A完成实例化之后,线程B并不知道,此时线程B同样会进行实例化,导致产生了多个单例对象。因此再次进行改进,在synchronized代码中再次添加instance == null的判断,即双重检查锁定(Double-Check Locking)。

tips: 使用双重检查锁实现懒汉式单例类,需要在静态成员变量instance之前增加修饰符volatile

使用双重检查锁实现的完整代码如下

public class LazySingleton{
    private volatile static LazySingleton instance = null;
    private LazySingleton(){}
    
    public static LazySingleton getInstance(){
        //如果实例为空则创建实例,否则直接返回实例
        if(instance == null){
            synchronized(LazySingleton.class){
                if(instance == null){
                    instance = new LazySingleton();
                }
            }
        }
        return instance;
    }
}
  • 静态内部类实现(推荐):JAVA支持Initialization on Demand Holder(IoDH)技术,在加载外部类的时候,不会创建内部类的实例,只有在外部类使用到内部类的时候才会创建内部类实例。
public class Singleton{
  
    private Singleton(){}
    
    private static class SingletonInner{
        private static final INSTANCE = new Singleton();
    }
    
    public static Singleton getInstance(){
        return SingletonInner.INSTANCE
    }
}

因为静态内部类的原因,创建INSTANCE的唯一性,线程安全性都由JVM来保证,并且由于加载外部类的时候不会创建内部类的实例,因此也实现了延迟加载,所以更推荐以此种方式实现单例模式。

三、单例模式在日常开发中应用场景

  1. 配置类:在应用程序中,配置类常常只需要一个实例来保存全局配置参数,如数据库配置、文件路径等。通过单例模式,保证配置类的实例在整个应用程序中是唯一的,避免了多次加载配置文件,提高了效率。
  2. 数据库连接池:数据库连接池管理数据库连接的创建、分配和释放。通过单例模式,可以确保连接池在整个应用程序中只有一个实例,从而有效地管理数据库连接资源,提高连接的复用率和应用程序的性能。
  3. 缓存:缓存系统用于存储临时数据,以加快数据的访问速度。通过单例模式,可以确保缓存系统只有一个实例,保证数据的一致性,并且方便集中管理和维护缓存数据。
  4. 序列号生成器:在一些需要生成全局唯一序列号的场景中,如订单号,事务ID等,单例模式可以确保序列号生成器只有一个实例,避免了生成重复序列号的问题。
  5. 设备管理:在需要与硬件设备交互的应用程序中,如打印机,摄像头等,通过单例模式,可以确保设备管理类只有一个实例,方便对设备的集中管理和控制。