设计模式(一)—— 单例模式(Singleton)
基于《Android 源码设计模式 解析与实战》第二版(何红辉 关爱民 著)的学习笔记
定义
确保某一类只有一个实例,且自行实例化并向整个系统提供这个实例
使用场景
- 需确保某个类有且只有一个对象的场景
- 需避免产生多个对象消耗过多资源
- 某类型对象应有且只有一个实例
如创建一个对象需要消耗的资源过多(IO、网络交互、数据库)
角色
- Client —— 高层客户端
- Singleton —— 单例类
特点
- 构造函数不对外开放,一般为private
- 通过一个静态方法或枚举类返回单例对象
- 确保单例类的对象有且只有一个,尤其是并发环境下
- 确保单例对象在反序列化时不会重新构建对象
实现方式
1. 饿汉模式模式
在生命静态对象时就已经初始化。因急于初始化,被称为饿汉模式
class Singleton {
private static final Singleton instance = new Singleton();
private Singleton(){}
static Singleton getInstance(){
return instance;
}
}
2. 懒汉模式
生命一个静态对象,并在用户第一次调用静态获取方法时进行初始化,用sychronized
关键字将获取方法标记为同步方法。
class Singleton {
private static final Singleton instance = null;
private Singleton(){}
static synchronized Singleton getInstance(){
if (instance == null)
instance = new Singleton();
return instance;
}
}
- 优点:只有在使用时才会被实例化,一定程度的节省资源
- 缺点:即使
instance
已被初始化,每次调用getInstance
都会进入同步,造成不必要的资源消耗
3. 双重锁模式、Double Check Lock (DCL)
在getInstance
方法中对instance进行两次判空
第一次判空只要为避免不必要同步,第二次的判断则为了在未实例化的情况下实现实例化。
class Singleton {
private static final Singleton instance = null;
private Singleton(){}
static Singleton getInstance(){
if (instance == null) {
synchronized(Singleton.class) {
if(instance == null)
instance = new Singleton();
return instance;
}
}
return instance;
}
}
- 优点:
- 资源库利用率高,既能够在需要时才初始化单例
- 保证线程安全,且对象初始化后不再进行同步锁
- 缺点:
- 第一次加载反应稍慢
- 高并发下可能发生DCL失效
DCL失效:
在调用到Singleton()
而在初始化instance
前跳转到其他线程,并在该线程完成instance
的赋值。此时切换回原线程,则会继续执行instance
的初始化。从而导致出错。可以用volatile
关键字解决此问题,不过该关键字也会影响性能。
静态内部类
为解决双重所失效(DCL失效)问题,可以用静态内部类的特性实现单例
只有在调用到静态内部类时,该静态内部类才会被虚拟机加载。
class Singleton {
private Singleton(){}
static Singleton getInstance(){
return SingletonHolder.instance;
}
static class SingletonHolder {
private static final Singleton instance = new Singleton();
}
}
- 第一次加载Singleton类时不会初始化instance
- 第一次调用
Singleton
的getInstance
方法时才会导致sInstance被初始化 - 保证线程安全
- 保证单例对象的唯一性
- 延迟了单例的实例化
- 是推荐使用的单例模式实现方式
枚举单例
通过枚举类特性实现单例
enum SingletonEnum {
INSTANCE;
void doSomething() {
System.out.printlin("do sth.")
}
}
- 枚举类在Java中与普通类是一样的,能拥有字段和自己的方法
- 默认是线程安全的
- 任何情况下都是一个单例
容器单例
容器形式的单例
class SingletonManager {
private static Map<String, Object> objMap = new HashMap<>();
private SingletonManager(){}
static void registerService(String key, Object instance) {
if(!objMap.containsKey(key))
objMap.put(key, instance);
}
static Object getService(String key) {
return objMap.get(key);
}
}
代码很清晰,而且是不是看起来很像Context
的getService
?
小结
- 优点:
- 减少内存开支
- 减少性能开销
- 避免资源多重占用
- 共享资源访问
- 缺点:
- 一般没有接口,拓展困难。若要拓展基本上只能修改代码
- 若单例持有
Context
,则可能造成内存泄漏。最好传递Application Content