什么是单例模式?
单例模式(Singleton) 是最常见也最简单的设计模式,它的目的就是在全局只生成一个类的实例。
什么场合用单例模式
应用中经常有多任务进行信息共享的需求,比如火车票售卖示例中,多个售票窗口其实共享一个票务池。如果一个票务池用一个类,那么这个类就只能用一个实例,否则多任务进行时会引起资源同步的问题。
另外,频繁创建和销毁的对象也可以用一个固定的实例,这样的好处是节省内存重复创建和销毁的开销,提高程序的稳定性。
面向对象的编程很容易实现单例模型,比如 Java、C++ 等等,本文以 Java 代码讲解。
单例模型的核心思想就是:私有化构造方法,只开放静态的获取方法。
单例模式的实现手段(Java)
饿汉式
// 饿汉式单例
public class SingletonHungry {
// 主动创建静态的私有实例
private static SingletonHungry singleton = new SingletonHungry();
// 私有的构造方法
private SingletonHungry(){}
//静态的公开的实例获取方法
public static SingletonHungry getInstance(){
return singleton;
}
}
饿汉式的例子一看就懂,不管三七二十一先创建了对象再说,不同的进程通过 getInstance 获取的都是同一个对象,所以是线程安全的。
但也有个不好的地方就是,如果某个类创建过程会消耗很多资源,但程序运行中没有调用过 getInstance 方法,那么就存在资源浪费的情况,如果一个系统存在非常多此类情况那么这个系统可能存在性能上的问题。
所以,我们需要一种延迟加载的功能。
懒汉式
public class SingletonLazy {
private static SingletonLazy instance;
private SingletonLazy (){}
public static SingletonLazy getInstance() {
if (instance == null) {
instance = new SingletonLazy();
}
return instance;
}
}
懒汉式在饿汉的基础上做了改进,先判断 instance 是否为空,如果为空则创建示例,否则直接返回。
懒汉式做到了延迟加载,非常适合单线程。
但多线程下面会存在问题,如果多个线程同时调用 getInstance 方法,可能存在同时判断 instance 变量是否为空的情况,上面的代码中很容易导致重复创建多个实例,这违背了单例模式的目的。
一般而言,我们会进行一些同步手段。
双检锁(DCL,double-checked locking)
public class SingletonDCL {
private volatile static SingletonDCL instance;
private SingletonDCL (){}
public static SingletonDCL getInstance() {
if (instance == null) {
synchronized (SingletonDCL.class) {
if (instance == null) {
instance = new SingletonDCL();
}
}
}
return instance;
}
}
DCL 进行两次 instance 的判断,并且利用了关键字 synchronized 。
试想当多个线程同时调用 getInstance 时,可能会存在同时判断 instance 为空的情况。
但因为 synchronized 关键字的存在,同一个时刻只能一个线程进入代码同步区域,其它线程会被阻塞。
被阻塞的线程重新获得进入代码临界区时,再次判断 instance 就好了。
静态内部类
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
这种方法也可以做到延迟加载,但是它又不同于饿汉式。
因为只有调用 getInstance 时,SingletonHolder 才会进行初始化。
之前做 Android 开发时,涉及到图片缓存加载的时候经常会看到一些开源组件有各类 ImageHolder 的代码,原理正是如此。
Android 源码中的单例模型
以 Android 系统版本为 9.0.0 代码为例,它的 framework 包中有一个 Singleton.java 文件。
package android.util;
/**
* Singleton helper class for lazily initialization.
*
* Modeled after frameworks/base/include/utils/Singleton.h
*
* @hide
*/
public abstract class Singleton<T> {
private T mInstance;
protected abstract T create();
public final T get() {
synchronized (this) {
if (mInstance == null) {
mInstance = create();
}
return mInstance;
}
}
}
它是一个抽象类,并且支持泛型,从而支持模板化的形式创建任意类型的对象的单例。
源码很简单,通过 DCL 实现了懒加载。
我们再看具体点。
xref: /frameworks/base/core/java/android/app/ActivityManager.java
/**
* @hide
*/
public static IActivityManager getService() {
return IActivityManagerSingleton.get();
}
private static final Singleton IActivityManagerSingleton =new Singleton() {@Overrideprotected IActivityManager create() {final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);final IActivityManager am = IActivityManager.Stub.asInterface(b);return am;
}
};
ActivityManager 是 Android 最核心的系统服务之一,它的代码有 4000 多行,上面是部分代码。它通过一个隐藏的 getService 调用而创建。
创建过程就是通过上面的 Singleton实现。
从这个角度看,Android Framework 代码其实也不是很难是嘛,相信自己,你也可以写出很多类似代码出来。
相关阅读:
聊聊面向对象的6大设计原则
工作多年,我对架构的一些理解