一、什么是单例模式
单例,即类的单一实例。
单例模式指的是在整个程序执行的过程中,此类只会存在唯一一个实例对象,供其余类调用。
简单说:不管你在代码的哪个位置、调用多少次这个类的创建方法,拿到的永远都是「同一个对象」,不会出现第二个。
由于单例模式关心的是类的创建模式,故单例模式是一种创建型设计模式。
二、为什么要有单例模式
(一)节约系统资源
单例模式要求全局只能有该类的一个实例,故而,也就减少了对象的创建,减少了对系统资源的消耗。
(二)统一数据源
我们知道,在开发中不可避免的需要定义一些类来存储全局配置、共享数据、运行状态、缓存数据等。这些类的核心诉求是:所有地方使用的「必须是同一份数据」 。但当我们涉及多线程场景时,有可能出现,线程A通过全局配置类的实例获取到属性A,而线程B获取到全局配置类的实例并对其属性A进行修改,此时若没有 对类进行单例设计 ,就会由于存在多个全局配置类的实例,导致线程A和线程B拿取到的属性数据不同的问题。
三、单例模式的实现方式
- 私有化构造方法和属性
- 统一提供对外的公开静态的实例获取方法
(一)饿汉单例
无论需不需要,都先创建。
即实例在类加载时就创建完成,随后不会再创建,无论此类的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创建了,故判空不通过,不会执行创建过程,陆续出来,拿到已经创建好的单例并返回。后面再有多线程访问此方法时,直接通过第一层的判空机制就可以跳过 **锁 ,**故而不会串行执行,也就是说双重检查锁机制,实现了第一次串行,后续并行,从而提高了性能。