单例模式
定义
确保单例类只有一个实例,并且这个单例类提供一个函数接口让其他类获取到这个唯一的实例。
为了确保只有一个实例,我们声明私有的构造方法,并使用static创建一个静态实例。这样就保证了其他地方没法使用构造方法,永远最多只有一个实例。当某个类的创建需要消耗大量资源,或者用来控制某些共享资源(数据库或者文件)的时候就可以使用单例模式。
另外单例模式又有两种实现方式。
饿汉模式:
即在声明的时候就将其进行初始化,这种是最简单的方式并且天生线程安全,但是不推荐,因为这种方式在类加载的时候就会进行初始化,一般来说使用单例模式的类占用资源都不少,这种方式浪费内存,尽量等到使用的时候再将其初始化,代码如下:
public class Singleton {
private Singleton() {} //私有的空构造方法
private static final Singleton instance = new Singleton1(); //创建静态的且唯一的实例
public static Singleton getInstance() {
return instance;
}
}
懒汉模式
在声明的时候并不初始化,而是在第一次使用getInstance() 时进行初始化,这样就避免了饿汉式的弊端。
懒汉式的实现也有好几种方式,接下来依次介绍,除了最后一种外都各自有一些问题。
懒汉式,线程不安全
这种方式是最基本的实现方式,但是这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized所以会出现多线程问题。
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
懒汉式,线程安全
这种方式能够在多线程中很好的工作,但是使用了synchronized效率很低,也不推荐。
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
双检锁/双重校验锁(推荐)(volatile是关键)
这种方式采用双锁机制(volatile和synchronized),安全且在多线程情况下能保持高性能。实际上我们只有在第一次使用 getInstance() 进行初始化时才会产生多线程问题,因此后面的 getInstance() 操作不应该进行多线程安全的保护,不然会损失大量的性能。
首先第一个 if 判断语句确保了只有未进行初始化才进行加锁初始化,第二个 if 确保了只初始化一次(if判断和new操作不是原子操作)。
同时 volatile 确保了不会因为cpu指令重排而得到未初始化的实例(new不是原子操作):new操作符实际上可以分成三个步骤:
- 分配内存
- 初始化对象
- 将引用指向内存地址 如果不使用volatile,则有可能出现1-3-2的情况,当执行完第二步,这时候另一个线程调用 getInstacne() ,那么在第一个if判断语句中就不会判空,会将一个没有完成初始化的实例返回给这个线程。
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
静态内部类实现
通过静态内部类也可以实现单例模式,而且和使用双重锁校验一样,不会出现多线程问题而且也会进行延迟初始化,同时代码也比较清晰易懂,但是同样也有无法传参的问题。
public class SingleTon {
private SingleTon() {} //私有的构造方法
//获取实例的静态方法
public static SingleTon getInstance() {
return SingleTonHolder.INSTANCE;
}
//静态内部类
private static class SingleTonHolder {
private static final SingleTon INSTANCE = new SingleTon();
}
}
使用静态内部类,并不会在类加载的时候就进行初始化,会在第一次对其进行使用的时候进行初始化,这样就实现了延迟初始化。同时,由于其静态的特性,永远只有一个线程会执行其代码,因此也保证了初始化时的线程安全。
但是其有一个致命的缺点,由于是静态内部类的形式去创建单例的,故外部无法传递参数进去,例如Context这种参数,所以,我们创建单例时,可以根据使用场景自行选择。