设计模式-单例
# 前言
确保某个类只有一个实例,而且自行实例化并向整个系统提供整个实例。说白了,就是"独苗"!什么是"自行实例化"?就是整个实例是在类内部自己创建的,不是在别的类中创建的。
单例谁都会写,而且五花八门,且随洒家来瞅瞅。
# 常见的单例模式
# 1 懒汉式单例
太尼玛懒了,你不要我就不创建,要了再说,应该叫"拖延症单例"更合适。
public class Singleton {
private static Singleton mInstance; //我就声明一下,不创建
private Singleton(){} //构造函数私有化,因为只能"自行实例化"。
public static Singleton getInstance(){
if(mInstance == null) mInstance = new Singleton();
return mInstance;
}
}
2
3
4
5
6
7
8
9
太easy了,没什么说的,优点是"有可能"省内存,万一没人要就不创建,就省了内存,缺点是如果多个人同时一起要,可能创建多个,说白了就是"非线程安全"。怎么解决?看下面的饿汉式。
# 2 饿汉式单例
先创建一个放着,你要了直接拿走完事,应该叫"猴急式单例"。
public class Singleton {
private static final Singleton mInstance = new Singleton(); //猴急的不行了,赶紧创建出来放着
private Singleton(){} //构造函数私有化,因为只能"自行实例化"。
public static Singleton getInstance(){
return mInstance;
}
}
2
3
4
5
6
7
8
更简单,缺点很明显,没人要也创建了,白占了内存,所以如果是个大对象,尽量别用这种模式,优点就是"线程安全",这里有个知识点,为什么是线程安全的呢,还记得我们在JVM类加载基础 (opens new window)里面讲的吗?
如果一个类里面有"静态赋值语句,静态代码块",那么这个类的class文件会生成一个<clinit>()方法来执行这些语句,这个方法会在类初始化的时候执行,并且"jvm会保证<clinit>()方法在多线程中被正确的加锁、同步",因为jvm保证了<clinit>()是线程安全的,所以静态赋值语句也是线程安全的,饿汉式单例中mInstance的创建就是静态赋值语句,所以它是线程安全的。
# 3 DCL单例
饿汉可能白费内存,懒汉不安全,所以还得我上场。
public class Singleton {
private static volatile Singleton mInstance; //不创建,一定要加"volatile"这个关键字,保证每次都实时从主内存读取
private Singleton(){} //构造函数私有化,因为只能"自行实例化"。
public static Singleton getInstance(){
if(mInstance == null) {
//先判断是否为null,为null才加锁,避免无辜加锁引起的性能问题
synchronized(Singleton.class) {
//加完锁了再判断一下,因为有可能我加锁的时候别人已经创建了
if(mInstance == null) mInstance = new Singleton();
}
}
return mInstance;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
代码有一点需要注意: 单例对象要加"volatile"保证每次都实时从主内存读取,因为synchronized不是实时的,是在同步块结束时才输入主内存的。
# 4 静态内部类单例
DCL需要加volatile,太费劲了,看我的
public class Singleton {
private Singleton(){} //构造函数私有化
public static Singleton getInstance(){
//返回静态内部类单例
return SingletonHolder.mInstance;
}
//私有静态内部类
private static class SingletonHolder {
//初始化单例
private static final Singleton mInstance = new Singleton();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
代码很简单,使用Singleton.class不会引起内部类SingletonHolder加载,只有调用Singleton.getInstance()才会引起SingletonHolder加载,加载的时候会初始化Singleton单例,巧妙的用内部类延迟了加载时机,做到"懒汉",同时内部类因为是"饿汉式",也就是利用了JVM<clinit>()是线程安全的特性,做到了"线程安全",可以说是最合适的单例模式了,极其推荐。
# 5 容器类单例
容器类单例有个优点,就是可以"查找",提供了一个"仓库"的管理功能。
public class SingletonPool{
private static Map<String,Object> map = new HashMap<>();
private SingletonPool(){}
public static void register(String key, Object instance) {
if(map.containsKey(key)) return;
map.put(key,instance);
}
public static Object getService(String key) {
return map.get(key);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
代码很简单,注入和获取,这样A可以注入自己的单例,B用的时候不需要用到A,直接从单例池中根据key获取即可,降低了耦合。Android源码中这样的例子很多,下面介绍。
# 6 枚举单例
最懒的一种单例,最快捷,也最安全的单例。
public enum Singleton {
mInstance
}
2
3
枚举默认是线程安全的,这个毋庸置疑,并且他也是绝对安全的,嘛意思?因为在序列化的过程中,反序列化会通过特殊途径创建类的实例,所以上述5种单例可能就会失效,但是枚举不会,怎么让上述5种单例也是序列化安全的?加入下面方法即可:
private Object readResolve() throws ObjectStreamException {
return mInstance;
}
2
3
因为反序列化的时候会调用readResolve(),通过它我们直接返回现有实例,就能避免重新序列化,但是枚举不存在这个问题。枚举单例的缺点也是"耗内存",但是如果确定代码中肯定要用到这个单例,那么用枚举完全是正确的选择。
# 源码中的单例
# AMS单例
我们一般怎么使用AMS呢,很简单:
ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
我们跟进源码(API21)发现,它最终调用到了ContextImpl.getSystemService(Context.ACTIVITY_SERVICE)中
@Override
public Object getSystemService(String name) {
//从一个Map中获取fetcher,然后使用fetcher来获取AMS,有get肯定有put,找!
ServiceFetcher fetcher = SYSTEM_SERVICE_MAP.get(name);
return fetcher == null ? null : fetcher.getService(this);
}
2
3
4
5
6
我们找下SYSTEM_SERVICE_MAP的定义和put()
private static final HashMap<String, ServiceFetcher> SYSTEM_SERVICE_MAP = new HashMap<String, ServiceFetcher>();
private static void registerService(String serviceName, ServiceFetcher fetcher) {
if (!(fetcher instanceof StaticServiceFetcher)) {
fetcher.mContextCacheIndex = sNextPerContextServiceCacheIndex++;
}
SYSTEM_SERVICE_MAP.put(serviceName, fetcher);
}
//在static块里注册了AMS对应的fetcher
static {
//这里注册了AMS对应的ServiceFetcher,重写了createService()函数,返回一个ActivityManager
registerService(ACTIVITY_SERVICE, new ServiceFetcher() {
public Object createService(ContextImpl ctx) {
return new ActivityManager(ctx.getOuterContext(), ctx.mMainThread.getHandler());
}});
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
接着看下ServiceFetcher的定义,他是ContextImpl的一个内部类:
static class ServiceFetcher {
int mContextCacheIndex = -1;
public Object getService(ContextImpl ctx) {
//获取ContextImpl的Service缓存
ArrayList<Object> cache = ctx.mServiceCache;
Object service;
synchronized (cache) {
if (cache.size() == 0) {
for (int i = 0; i < sNextPerContextServiceCacheIndex; i++) {
cache.add(null);
}
} else {
//获取对应的service,有就返回
service = cache.get(mContextCacheIndex);
if (service != null) return service;
}
//没有就调用cerateService()来创建
service = createService(ctx);
//放入缓存
cache.set(mContextCacheIndex, service);
return service;
}
}
public Object createService(ContextImpl ctx) {
throw new RuntimeException("Not implemented");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
我们捋一下逻辑:
1 Activity通过getSystemService(Context.ACTIVITY_SERVICE)获取AMS,最终调用到了ContextImpl的getSystemService()里面
2 ContextImpl里面有个map,key是AMS对应的名字"ACTIVITY_SERVICE",value是一个Fetcher
3 Fetcher有个createService函数,可以创建AMS;还有个getService()函数,可以获取AMS,它会先从缓存获取,获取不到就调用createService()创建并缓存起来
4 ContextImpl就先获取这个Fetcher,调用他的getService()来获取AMS
5 ContextImpl在加载的时候就会将这个Fetcher创建出来放入map中。
这就是很经典的容器式单例,不过这里优化了,放的不是直接的单例,而是放的工厂(key-factory),需要的时候用对应的工厂去创建,这就做到了懒加载,在任意地方想用这个单例,直接getSystemService(key)就行,而不是ActivityManager.getInstance();降低了耦合。
设计模式威力无穷,万岁万万岁,大胆的用吧!