设计模式-单例模式
2020-10-14 本文已影响0人
h2coder
为什么要用单例模式?什么时候用?
- 单例模式一般用于,创建时,需要创建比较多的对象,消耗比较多的内存时使用。
- 当多处调用都需要同一个对象进行状态处理时,内存存在一个对象比较合理,而不是每次进行状态切换,就创建一个实例,或销毁之前的实例再重新创建一个。
- 平时我们写代码时,例如推送、音频播放等,一般都会写一个Manager管理类,这些类包含了比较多的初始化操作,一般会做成单例。(例如Android的context.getSystemService(name),获取系统服务)。
怎样实现单例
- 一般会将构造方法私有,外部无法使用new关键字进行创建实例。
- 将实例静态成员化,类共享。
- 提供一个静态方法(类函数),例如方法名为getInstance(),返回当前实例。内部进行非空判断,如果非Null则直接返回非空的唯一实例,如果为Null,则创建并将其赋值到静态的成员变量中。
实现单例的几种方式
- 饿汉式
- 懒汉式
- synchronized同步+volatile+Double Check
- 静态内部类
- 枚举
饿汉式
- 一开始就初始化唯一的实例,缺点是如果该类构造时,如果存在过多的处理,会导致加载该类比较慢,可能会有性能问题。
public class SingleInstance {
private static SingleInstance sInstance = new SingleInstance();
private SingleInstance() {
}
public static SingleInstance getInstance() {
return sInstance;
}
}
懒汉式
- 只有当第一次调用getInstance()时才实例化单例实例,下次再进来会进行判断不为null直接返回。(未考虑到多线程问题,多线程下依然会存在多个实例)
public class SingleInstance {
private static SingleInstance sInstance;
private SingleInstance() {
}
public static SingleInstance getInstance() {
if (null == sInstance) {
sInstance = new SingleInstance();
}
return sInstance;
}
}
synchronized同步+volatile+Double Check
- volatile关键字保证线程之间,单例变量的可见性,可见性是什么呢,意思是当其中一个线程去修改这个共享的单例变量时,其他线程能读到最后改变的值。
- synchronized则是同步关键字,保证在判空条件语句时,只有一个线程能执行到。
public class SingleInstance {
private static volatile SingleInstance sInstance;
private SingleInstance() {
}
public static SingleInstance getInstance() {
if (null == sInstance) {
synchronized (SingleInstance.class) {
if (null == sInstance) {
sInstance = new SingleInstance();
}
}
}
return sInstance;
}
}
静态内部类
- 利用Java中类的初始化在类加载时才触发,我们就可以将实例放在内部类中,getInstance()方法调用时,才初始化SingleInstanceHolder这个内部类,做到了延时初始化的作用。并且JVM在初始化类时,会加锁,确保了多线程的问题。(Java是多线程的,可能会出现多个线程同时去初始化一个类,会给Class做初始化锁处理)。
public class SingleInstance {
private SingleInstance() {
}
public static SingleInstance getInstance() {
return SingleInstanceHolder.sInstance;
}
private static class SingleInstanceHolder {
private static SingleInstance sInstance = new SingleInstance();
}
}
枚举
- 枚举的构造方法本身就是私有的,保证外部不能创建实例。
- 枚举和Class一样,可以拥有自己的成员变量和方法。
- 枚举最后生成的类实质就是class,并且继承Enum类,在static静态代码块中初始化枚举实例。当一个Java类第一次被调用静态代码块时,都是线程安全的。
- 基于Class的单例,在反射或反序列化下,依然不能保证单例。反射能去掉private访问修饰符,反序列化后的对象也是重新new出来的。而枚举则不是,枚举的序列化不是class中常规的序列化手段。
public enum SingleInstance {
INSTANCE;
public void doSomthing() {
}
}
工厂方式
- 像Android中的context.getSystemService(name),获取系统服务,多个服务单例,我们可以建立一个工厂类,提供一个IHelper接口(或者IManager),首先工厂单例,保证只有一个工厂实例,拥有静态的一个HashMap,提供注册、注销方法,以及查找方法,保证Map中只有一个Helper实例。
- 因为这个Map是单例的,即使输入进来的对象不是作为静态变量,因为如此,间接成为单例。
public class HelperFactory {
private static HashMap<Class<? extends IHelper>, IHelper> sHelpers;
static {
sHelpers = new HashMap<>();
}
private HelperFactory() {
}
private static final class SingleHolder {
private static final HelperFactory INSTANCE = new HelperFactory();
}
public static HelperFactory getInstance() {
return SingleHolder.INSTANCE;
}
public void registerHelper(Class<? extends IHelper> clazz, IHelper helper) {
sHelpers.put(clazz, helper);
}
public void unregisterHeler(Class<? extends IHelper> clazz) {
sHelpers.remove(clazz);
}
public <T extends IHelper> T findHelper(Class<T> clazz) {
IHelper helper = sHelpers.get(clazz);
if (helper != null) {
return (T) helper;
}
throw new IllegalStateException("not find " + clazz.getName() + " Helper instance");
}
}
Android源码中的单例模式
在ActivityManager中有这么一段代码,getService()方法,获取IActivityManagerSingleton对象,调用get()方法获取一个Singleton实例,Singleton的泛型为IActivityManager,那Singleton是什么类呢,听名字像是单例的意思,我们点击Singleton会发现进不去,因为这个类被标记为hide了,所以在android.jar中没有。
/**
* @hide
*/
@UnsupportedAppUsage
public static IActivityManager getService() {
return IActivityManagerSingleton.get();
}
@UnsupportedAppUsage
private static final Singleton<IActivityManager> IActivityManagerSingleton = new Singleton<IActivityManager>() {
@Override
protected IActivityManager create() {
final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
final IActivityManager am = IActivityManager.Stub.asInterface(b);
return am;
}
};
Singleton的代码路径:/frameworks/base/core/java/android/util/Singleton.java,发现Singleton的代码不多,也就10来行。
Singleton为一个抽象类,有一个create()抽象方法,被get()方法调用。当实例为null时,才调用create()创建并保存起来,有实例则直接返回,并且加上了synchronized同步关键字来处理多线程同步问题。
/**
* 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;
}
}
}