单例模式(Singleton Pattern)

定义

确保某一个类只有一个实例,且自行实例化并向整个系统提供整个实例。

使用场景

确保某个类有且只有一个对象的场景,避免产生多个对象消耗过多的资源,或某种类型的对象只应该有一个。

优缺点

优点:

  • 减少内存开销。若一个类的对象需要频繁地创建和销毁,利用单例可大大减少开支。

  • 减少系统的性能开销。当一个对象的产生需要比较多的资源,可利用单例,让其长时间或永久驻留内存。

  • 避免对资源的多重占用。如只有一个文件实例在内存中,避免对同一个文件的同时写操作。

  • 在系统设置全局的访问点,优化和共享资源访问。

缺点:

  • 没有接口,扩展困难

  • 单例对象若持有Context,容易引起内存泄露。因此传递给单例对象的Context最好是Application级别的Context。

注意事项

实现的关键点

  1. 构造函数不对外开放,一般为private。

  2. 通过一个静态方法或枚举返回单例类对象。

  3. 确保单例类的对象有且只有一个,尤其是在多线程环境下。

  4. 确保单例类对象再反序列化时不会重新构建对象。

单例的反序列化

  • 通过序列化可将一个对象写到磁盘。之后再读出来,通过反序列化获得该对象。

  • 即使构造函数是私有的,反序列化提供一个很特别的钩子函数private Object readResolve(),也可创建类的一个新的实例。

  • 要杜绝单例对象在被反序列化时重新生成对象,必须重写此钩子函数。

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

实现方式

1. 饿汉模式

类被加载时就初始化实例。

public class Singleton {
    public static final Singleton instance = new Singleton();
    
    private Singleton() {}
}
1
2
3
4
5
public class Singleton {
    public static final Singleton instance = null;
    
    private Singleton() {}
    
    static {
        instance = new Singleton();
    }
}
1
2
3
4
5
6
7
8
9

2. 静态工厂方法模式

单例只有在使用时才会被实例化。

public class Singleton {
    private static Singleton instance;
    
    private Singleton() {}
    
    public static synchronized Singleton getInstance() {
        if (null == instance) {
            instance = new Singleton();
        }
        return instance;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

注意,每次调用getInstance()都需要同步,造成不必要的同步开销。

3. Double Check Lock模式

使用volatile双重检测对上一种方式进行优化。

public class Singleton {
    private static volatile Singleton instance = null;
    
    private Singleton() {}
    
    public static Singleton getInstance() {
        if (null == instance) {
            sychronized(Singleton.class) {
                if (null == instance) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

instance的两次判空:

  1. 避免不必要的同步;

  2. 在null情况下才创建实例。

instance = new Singleton()看似一句代码,但实际上它并不是一个原子操作,而是由3个独立的指令所组成:

  1. Singleton的实例分配内存。

  2. 调用Singleton的构造函数,初始化成员变量。

  3. instance指向分配的内存空间。

Java编译器允许处理器乱序执行,以及JDK1.5之前JMM中Cache、寄存器到主内存回写顺序的规定,上面的第2和第3步的顺序是无法保证的。也就是说,执行顺序可能是1->2->31->3->2

若线程A执行完1->3,然后线程B直接取走instance,在使用时会出错。这是因为此时instance不为null,指向某块内存,但其成员变量仍未初始化。

因此,需要使用valatile关键字声明instance对象,既保证了此变量对所有线程的可见性,又禁止指令重排序优化(从JDK 1.5起)。

4. 静态内部类模式(推荐)

public class Singleton {
    private Singleton() {}
    
    public static Singleton getInstance() {
        return SingletonHolder.instance;
    }
    
    private static class SingletonHolder {
        private static final Singleton instance = new Singleton();
    }
}
1
2
3
4
5
6
7
8
9
10
11

第一次调用Singleton的getInstance()时,才会导致JVM加载SingletonHolder类,从而初始化instance。

这种方式不仅能够确保线程安全、单例对象的唯一性,同时也延迟了单例的实例化。

5. 枚举模式

public enum Singleton {
    INSTANCE;
    
    public void doSth() {
    }
}
1
2
3
4
5
6

通过Singleton.INSTANCE直接获取单例,并可调用其方法。如Singleton.INSTANCE.doSth();

枚举模式的优点:

  • 写法简单。

  • 枚举实例的创建是线程安全的,且在任何情况下它都是一个单例。

6. 容器模式

在程序的初始,将多种单例类型对象注入到一个统一的管理类中。

public class SingletonMgr {
    private static Map<String, Object> insMap = new HashMap<String, Object>();
    
    private SingletonMgr() {}
    
    public static void putInstance(String key, Object instance) {
        if (!insMap.containsKey(key)) {
            insMap.put(key, instance);
        }
    }
    
    public static Object getInstance(String key) {
        return insMap.get(key);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

Android中的单例模式

Android中的各系统服务单例是通过容器模式来管理的。

class ContextImpl extends Context {

    static class ServiceFetcher {
        
        public Object getService(ContextImpl ctx) {
            // 服务是否存在,若存在则返回;若不存在则创建后返回。
        }
        
        public abstract T createService();
    }
    
    // 1. 系统服务容器
    private static final HashMap<String, ServiceFetcher> SYSTEM_SERVICE_MAP = new HashMap<String, ServiceFetcher>();
    
    // 2. 注册服务器
    private static void registerService(String serviceName, ServiceFetcher fetcher) {
        SYSTEM_SERVICE_MAP.put(serviceName, fetcher);
    }
    // 3. 注册各系统服务(静态语句块,第一次加载此类时执行,只执行一次)
    static {
        // 注册LayoutInflater Service
        registerService(Context.LAYOUT_INFLATER_SERVICE, new ServiceFetcher() {
            public Object createService(ContextImpl ctx) {
                return new PhoneLayoutInflater(ctx.getOuterContext());
            }
        });
        // 注册其他系统服务...
    }
    // 4. 根据key获取对应的系统服务
    public Object getSystemService(String name) {
        ServiceFetcher fetcher = SYSTEM_SERVICE_MAP.get(name);
        return fetcher == null ? null : fetcher.getService(this);
    }
}
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
29
30
31
32
33
34