单例模式
单例模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
注意:
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。
意 图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
主要解决:一个全局使用的类频繁地创建与销毁。
何时使用:当您想控制实例数目,节省系统资源的时候。
如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
关键代码:构造函数私有化
单例模式可以说是设计模式里最常用的一种了,它的实现中最重要的两点是对外开放单一实例和构造函数私有化,其中构造函数私有化是该类单一实例的保证,只有实现了以上两点才可以发挥单例的作用。目前来说单例模式有七种实现方式,每种都针对不同的场景,但本篇仅拿出其中常用的四种单例实现方式进行总结介绍。
一、懒汉式
懒汉式包含两种,一种是只能运行在非并发场景内,实现起来也最简单,在项目无并发需求的场景内,可使用此方式简单实现单例。
public class LazySingletonInstance {
private static LazySingletonInstance mInstance;
//构造函数私有化
private LazySingletonInstance(){}
//实例对外开放
public static LazySingletonInstance getInstance(){
//保证单一实例
if (mInstance == null){
mInstance = new LazySingletonInstance();
}
return mInstance;
}
}
在第一种的基础上,为LazySingletonInstance 方法加上Synchronized关键字即可运行于并发场景,但同时由于各个线程都要竞争锁,但99% 情况下不需要同步,因此会带来性能问题。
public class LazySingletonInstance {
private static LazySingletonInstance mInstance;
//构造函数私有化
private LazySingletonInstance(){}
//实例对外开放
public static synchronized LazySingletonInstance getInstance(){
//保证单一实例
if (mInstance == null){
mInstance = new LazySingletonInstance();
}
return mInstance;
}
}
二、饿汉式
它基于 classloader 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用 getInstance 方法。在并发项目中,饿汉式是最常用的方式,实现简单,不需要加锁来解决并发问题,执行效率远高于懒汉式。 但同时,使用类加载机制进行初始化也造成了更多的内存占用,容易产生垃圾对象 。
//最常用
public class HungrySingletonInstance {
private static HungrySingletonInstance mInstance = new HungrySingletonInstance();
//构造函数私有化
private HungrySingletonInstance(){}
//对外开放实例
public static HungrySingletonInstance getInstance(){
return mInstance;
}
}
三、双重校验锁(Double-Check_Lock,DCL)
该种方式是在并发懒汉方式上针对其缺陷进行的改进,通过DCL实现单例的关键点有以下3个:
- 内部实例需添加 volatile 关键字,以保证内存可见性
- 去除getInstance()方法锁
- 对内部实例双重判空检查,并对实例化代码块进行加锁。
DCL的优势在于依然采用懒汉式的实例化方式,不会产生垃圾对象,同时,在并发环境中,也可以保持高性能,仅在第一次实例化时需要竞争锁,而在实例化完成后,就不再需要通过竞争机制去拿实例,效率很高。
而volatile关键字则是为了防止并发环境下发生在双重判空检查之间的内存可见性问题。
public class DCLSingletonInstance {
//volatile 关键字保证内存可见性
private static volatile DCLSingletonInstance mInstance;
private DCLSingletonInstance(){}
public static DCLSingletonInstance getInstance(){
if (mInstance == null) {
//同步实例化代码块
synchronized (DCLSingletonInstance.class){
if (mInstance == null){
mInstance = new DCLSingletonInstance();
}
}
}
return mInstance;
}
}
静态内部类方式
静态内部类的实现方式是针对饿汉式的缺陷做出的改进,它将对象的实例化存放于静态内部类中,然后在getInstance()方法中,通过静态内部类获取当前实例。这种方式同样利用了 classloader 机制来保证初始化 instance 时只有一个线程,它跟饿汉式式不同的是:在饿汉式方法中只要 HungrySingletonInstance 类被装载了,那么 mInstance 就会被实例化,而这种方式是 Singleton 类被装载了,mInstance 不一定被初始化。因为 RegisterSingletonInstance类没有被主动使用,只有通过显式调用 getInstance() 方法时,才会显式装载 RegisterSingletonInstance类,从而实例化 mInstance。想象一下,如果实例化 instance 很消耗资源,所以想让它延迟加载,另外一方面,又不希望在 Singleton 类加载时就实例化,因为不能确保 Singleton 类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化 instance 显然是不合适的。这种场景下,显然使用静态内部类的方式更为适合。
public class RegisterSingletonInstance {
//静态内部类中持有实例化对象
private static class InstanceHolder {
private static RegisterSingletonInstance mInstance = new RegisterSingletonInstance();
}
private RegisterSingletonInstance(){}
public static RegisterSingletonInstance getInstance(){
//显式调用内部类
return InstanceHolder.mInstance;
}
}