设计模式之单例模式

一、什么是单例模式

单例,即类的单一实例。

单例模式指的是在整个程序执行的过程中,此类只会存在唯一一个实例对象,供其余类调用。

简单说:不管你在代码的哪个位置、调用多少次这个类的创建方法,拿到的永远都是「同一个对象」,不会出现第二个。

由于单例模式关心的是类的创建模式,故单例模式是一种创建型设计模式

二、为什么要有单例模式

(一)节约系统资源

单例模式要求全局只能有该类的一个实例,故而,也就减少了对象的创建,减少了对系统资源的消耗。

(二)统一数据源

我们知道,在开发中不可避免的需要定义一些类来存储全局配置、共享数据、运行状态、缓存数据等。这些类的核心诉求是:所有地方使用的「必须是同一份数据」 。但当我们涉及多线程场景时,有可能出现,线程A通过全局配置类的实例获取到属性A,而线程B获取到全局配置类的实例并对其属性A进行修改,此时若没有 对类进行单例设计 ,就会由于存在多个全局配置类的实例,导致线程A和线程B拿取到的属性数据不同的问题。

三、单例模式的实现方式

  1. 私有化构造方法和属性
  2. 统一提供对外的公开静态的实例获取方法

(一)饿汉单例

无论需不需要,都先创建。

即实例在类加载时就创建完成,随后不会再创建,无论此类的getInstance()方法有没有被调用,即此类的实例有没有被使用,都会创建。

java 复制代码
public class Singleton {
    //静态属性,在Singleton类加载时就已创建完成
    private static final Singleton instance = new Singleton(); 

    private Singleton() {
        // 私有构造方法,防止外部实例化
    }

    //对外提供统一的获取唯一实例的静态方法    
    public static Singleton getInstance() {
        return instance;
    }

}

饿汉模式虽然便利,不存在多线程环境下的单例重复创建的风险,因为其在类加载时就已经创建完成,但也存在一个弊端,可能会浪费内存,当程序中没有任何地方使用此类的实例,即getInstance()方法没有被调用过,但单例还是创建了。

(二)懒汉单例

第一次需要时再创建单例。

即在第一次有类调用获取此类的实例的方法时,才创建此类的实例,后面就无需创建了,这样做的好处是,当没有类需要创建此类的对象时,此类就不会创建实例,可以节约一定的内存资源。

但懒汉单例需要额外的锁机制来保证其单例的特性,因为其实例的创建,是依赖于其余类调用其公开的静态方法来实现的,这就意味着,在多线程环境中,但此类还没被创建时,若多个线程同时调用了其实例创建方法,就会导致创建出多个实例!故,懒汉单例一定需要额外的锁机制来保证!

1.单层锁机制
java 复制代码
public class Singleton {
    private static Singleton instance;
    
    private Singleton() {
        // 私有构造方法,防止外部实例化
    }
    // 使用了同步关键字来确保线程安全, 可能会影响性能
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

为实例的创建方法getInstance()增加了synchronized 关键字,这样在多线程环境中,当单例还未被创建时,多个线程同时访问getInstance()方法时,当线程A进入此方法时,其余线程就会被卡在外部等待,直至线程A执行完成此方法,此时单例创建完毕,其余线程再进来时,通过判空机制,就可以直接拿到单例了,不会出现重复创建的现象了。

但这样是会**影响性能(浪费时间)**的,因为我们所说在重复创建实例的风险,其实只在第一次创建时存在,一旦单例创建完成后,即使在多线程环境下多个线程同时调用此方法,通过判空机制,也不会再创建对象了,但由于方法锁的存在,在单例创建完成后(重复创建的风险消失后),多线程环境下,对此方法的访问只能是串行的,即一个一个的进入此方法,这无疑会降低性能。

故而,有了下面的双重检查锁机制的懒汉模式。

2.双重检查锁机制
java 复制代码
public class Singleton {
    private static volatile Singleton instance;
    
    private Singleton() {
        // 私有构造方法,防止外部实例化
    }
    
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

双重检查锁机制,可以在解决单例重复创建的风险的同时,提高性能。

双重检查锁机制,将 放在了方法里面,这样,在多线程环境下,getInstance()方法第一次调用时,当线程A第一个进入了锁内部时,其余线程就会被卡在第一层判空内部,由于此时实例未被创建,线程A会进入第二层判空内部,执行实例创建,直至线程A创建完成实例后,其余被卡在第一层判空内部的线程才会陆续进入,由于实例已经被线程A创建了,故判空不通过,不会执行创建过程,陆续出来,拿到已经创建好的单例并返回。后面再有多线程访问此方法时,直接通过第一层的判空机制就可以跳过 **锁 ,**故而不会串行执行,也就是说双重检查锁机制,实现了第一次串行,后续并行,从而提高了性能。

相关推荐
茶本无香8 小时前
设计模式之十二:模板方法模式Spring应用与Java示例详解
java·设计模式·模板方法模式
wangmengxxw14 小时前
设计模式 -详解
开发语言·javascript·设计模式
进击的小头14 小时前
设计模式落地的避坑指南(C语言版)
c语言·开发语言·设计模式
短剑重铸之日14 小时前
《设计模式》第五篇:策略模式
java·后端·设计模式·策略模式
HL_风神16 小时前
C++设计模式学习-工厂方法模式
c++·学习·设计模式
琹箐16 小时前
设计模式——策略模式
设计模式·策略模式
YigAin1 天前
Unity23种设计模式之 命令模式
设计模式·命令模式
twj_one1 天前
java中23种设计模式
java·开发语言·设计模式
香芋Yu1 天前
【深度学习教程——01_深度基石(Foundation)】05_数据太多怎么吃?Mini-batch训练的设计模式
深度学习·设计模式·batch
进击的小头2 天前
设计模式组合应用:传感器数据采集与处理系统
c语言·设计模式