一、什么是 单例模式
从字面意思上来说,单例模式就是 只存在一个实例 的模式。只存在一个实例的好处是 内存中只存在一个实例,减少了内存的开销,避免了对象的频繁的创建于销毁。可以帮助我们控制实例的数目,节约系统资源。
二、常用的三种单例模式
第一种:线程安全-懒汉式
所谓的懒汉式,从字面意思上而言是一种慵懒的方式(被动)创建实例,即当你需要的时候且没有所需要是实例时,系统才去创建实例。
因为用被动的方式创建实例,在创建实例的过程中存在线程安全的问题,这里所说的线程安全问题是指存在多个实例被创建的情况,所以我们需要用的 synchronized 关键字保证线程安全。
// 实例
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() {
// 此处存在线程安全,当多个线程同时调用此方法时,可能存在
// 当 if 没有执行完,下一个线程右接着执行,造成多次实例化
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
第二种:线程安全-饿汉式
所谓的饿汉式,从字面意思上而言是一种主动的创建方式,即一开始就已经进行了实例化,当多个线程进行调用时,饿汉式不存在对象没有被实例化的情况,即在线程调用的过程中不存在实例化的过程,这也就意味着并不会存在线程安全的问题。
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){}
// 线程在每次调用的时候,直接获取已经实例化之后的对象,不会存在线程安全的问题。
public static Singleton getInstance() {
return instance;
}
}
第三种:双检锁/双重校验锁
所谓的 双重校验锁 指的是具有两重检验:因为会有两次检查 instance == null,一次是在同步块外,一次是在同步块内。为什么在同步块内还要再检验一次?因为我们并没有在 getSingleton() 方法上使用 synchronized 关键字进行加锁,所以可能会有多个线程一起进入同步块外的 if,如果在同步块内不进行二次检验的话就会生成多个实例。
使用 volatile 关键字保证当由 volatile 关键字修饰的变量发生变化后,使用这个变量的线程会第一时间获取最新的值。从而保证了一致性。
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
// 因为没有在这个方法上加锁(synchronized),所以存在多个线程同时进入这个 if 的可能。
if (singleton == null) {
// 多个线程会依次执行此同步代码块。
synchronized (Singleton.class) {
// 倘若不在此 if 进行判断,那么同时进行上层 if 的线程会在依次执行同步代码块
// 的同时都会都会一个实例。
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}