设计模式-单例

3/1/2021 设计模式单例

# 前言

确保某个类只有一个实例,而且自行实例化并向整个系统提供整个实例。说白了,就是"独苗"!什么是"自行实例化"?就是整个实例是在类内部自己创建的,不是在别的类中创建的。

单例谁都会写,而且五花八门,且随洒家来瞅瞅。

# 常见的单例模式

# 1 懒汉式单例

太尼玛懒了,你不要我就不创建,要了再说,应该叫"拖延症单例"更合适。

public class Singleton {
    private static Singleton mInstance; //我就声明一下,不创建
    private Singleton(){} //构造函数私有化,因为只能"自行实例化"。

    public static Singleton getInstance(){
        if(mInstance == null) mInstance = new Singleton();
        return mInstance;
    }
}
1
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;
    }
}
1
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;
    }
}
1
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();
    }
}
1
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);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

代码很简单,注入和获取,这样A可以注入自己的单例,B用的时候不需要用到A,直接从单例池中根据key获取即可,降低了耦合。Android源码中这样的例子很多,下面介绍。

# 6 枚举单例

最懒的一种单例,最快捷,也最安全的单例。

public enum Singleton {
    mInstance
}
1
2
3

枚举默认是线程安全的,这个毋庸置疑,并且他也是绝对安全的,嘛意思?因为在序列化的过程中,反序列化会通过特殊途径创建类的实例,所以上述5种单例可能就会失效,但是枚举不会,怎么让上述5种单例也是序列化安全的?加入下面方法即可:

private Object readResolve() throws ObjectStreamException {
    return mInstance;
}
1
2
3

因为反序列化的时候会调用readResolve(),通过它我们直接返回现有实例,就能避免重新序列化,但是枚举不存在这个问题。枚举单例的缺点也是"耗内存",但是如果确定代码中肯定要用到这个单例,那么用枚举完全是正确的选择。

# 源码中的单例

# AMS单例

我们一般怎么使用AMS呢,很简单:

ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
1

我们跟进源码(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);
}
1
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());
                }});
}
1
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");
    }
}
1
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();降低了耦合。

设计模式威力无穷,万岁万万岁,大胆的用吧!

Last Updated: 1/28/2022, 2:19:00 PM