模式介绍
说起单例模式,相信对于从事开发的同学来说再熟悉不过了,单例模式是一种相对来说简单且应用最广泛的设计模式,该模式确保我们使用的单类对象类只有一个实例存在,避免创建过多对象实例造成资源浪费,熟悉Android开发的同学都知道,Android中Application类其实就属于单例模式,同时在平时开发中,我们也会将图片加载类、网络请求类、数据库操作类等等设计成单例模式。
模式定义
确保某一个类只有一个实例,而且自行实例化并向整个系统提供该实例对象,如何确保只有一个实例?让类自身负责保存该类的唯一实例,同时保证单例类的构造方法私有化,然后提供一个公开的静态方法访问这个唯一的实例。
实现方式
单例模式的实现方法有很多种,分别有饿汉模式、懒汉模式、DCL(双重解锁)模式、静态内部类模式、枚举单利模式、接下来我们一一介绍这几种模式,并分析每种模式的优缺点。
饿汉模式
public class Singleton {
private Singleton() {
}
private static Singleton instance= new Singleton();
public static Singleton getInstance() {
return instance;
}
}
饿汉模式在类加载的时候就对实例进行了初始化,由于单例对象一开始就得以创建,所以当我们调用起实例时,反应速度较快,同时饿汉模式也保证了实例对象的唯一性,多线程情况下不会创建多个实例对象。正是由于其在类加载的时候就创建了实例对象,所以不管我们有没有调用该单例对象,单例类都会去创建实例对象,从而造成了系统资源的浪费。
懒汉模式
public class Singleton {
private Singleton() {
}
private static Singleton instance;
public static synchronized Singleton getInstance() {
if (instance == null ) {
instance = new Singleton();
}
return instance;
}
}
懒汉模式相比较饿汉模式来说,采用了懒加载的方式,只有在使用的时候才会进行实例化,节约了系统资源。同时在getInstance方法上加了synchronized 关键字来保证多线程调用,但是这样我们每次调用getInstance方法时都会进行同步,造成一些不必要的同步的开销,导致系统的性能受到一定的影响。
DCL(Double Check Lock)
public class Singleton {
private Singleton() {
}
private static volatile Singleton instance;
public static Singleton getInstance() {
if (instance == null ) {
synchronized (Singleton.class) {
if (instance == null ) {
instance = new Singleton();
}
}
}
return instance;
}
}
DCL单例实现方式同懒汉模式一样,第一次执行getInstance方法时对对象进行实例化,但相对懒汉模式来说,DCL对getInstance方法进行了优化,并没有像懒汉模式一样每次都进行同步来保证线程安全,而是进行了俩次的空判断,第一次判断空是为了避免进行不必要的同步,如果不为空,直接返回实例,第二次判空主要是为了创建实例对象,同时我们发现在静态成员变量instance前面加了一个volatile关键字,接下来详细分析下为什么加volatile?如果不加会有什么问题?
要分析以上问题,首先我们要了解对象的创建过程,实例化一个对象其实可以分为三个步骤,也就是说这行代码instance = new Singleton()
其实是分了以下三步执行:
- 为SingleInstance的实例分配内存空间;
- 初始化对象(调用构造方法,初始化成员字段);
- 将分配好的内存空间赋值给对应的引用,也就是instance。
这里不得不提到一个概念叫做指令重排序,首先这里不对指令重排序做详细介绍,现在来分析以上三个步骤执行顺序,如果上面三个步骤按照正常1-2-3来执行的话,不会发生任何问题,但是指令重排序可能会导致执行顺序变为1-3-2,假设我们现在有A、B俩个线程同时执行,如果A按照1-3-2顺序执行的话,当3执行完成后,这时instance已经不为空了,那么如果B这时候去获取该对象实例的话就会直接返回instance,这就使用时就会出错,造成DCL失效。当然我们这里加了volatile关键字后可以避免这种情况的发生,volatile关键字可以防止指令重排序,当然volatile还有其他作用,比如保证原子性、实现可见性。Java 并发编程:volatile的使用及其原理这篇文章对volatile关键字作了详细解释,感兴趣的同学可以去查看
静态内部类模式
public class Singleton {
private Singleton() {
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
static class SingletonHolder {
private static final Singleton instance = new Singleton();
}
}
静态内部类是目前比较推崇的一种单例模式实现方式,首次加载类的时候并不会对实例进行初始化,只有在第一次调用getInstance方法时才会去加载SingletonHolder类 ,并对对象实例进行初始化,这种方式延迟了对象的实例化,同时保证了线程安全性与对象的唯一性。