微信公众号

设计模式之单例模式

设计模式

设计模式(design pattern)是对软件设计中普遍存在(反复出现)的各种问题,所提出的解决方案。
这个术语是由埃里希·伽玛(Erich Gamma)等人在1990年代从建筑设计领域引入到计算机科学的。
《设计模式》一书原先把设计模式分为创建型模式、结构型模式、行为型模式,把它们通过授权、聚合、诊断的概念来描述。

创建范例

创建范例全部是关于如何创建实例的。这组范例可以被划分为两组:
类创建范例及对象创建范例。类创建实例在实例化过程中有效的使用类之间的继承关系,
对象创建范例则使用代理来完成其任务。包括:

  • 抽象工厂(Abstact Factory)
  • 构造器(Builder Pattern)
  • 工厂方法(Factory Method pattern)
  • 原型(Prototype pattern)
  • 单例模式(Singleton pattern)

结构范例

这组范例都是关于对象之间如何通讯的。包括:

  • 职责链(Chain-of-responsibility pattern)
  • 命令(Command pattern)
  • 翻译器(Interpreter pattern)
  • 迭代器(Iterator pattern)
  • 仲裁器(Mediator pattern)
  • 回忆(Memento pattern)
  • 观察者(Observer pattern)
  • 状态机(State pattern)
  • 策略(Strategy pattern)
  • 模板方法(Template method pattern)
  • 参观者(Visitor)

单例模式

什么是单例模式?确保一个类只有一个实例,并提供对该实例的全局访问,其构造函数私有化。

七种实现方式

各种写法各有利弊,让我们看看具体写法:

懒汉模式,线程不安全

1
2
3
4
5
6
7
8
9
10
11
12
public class SingletonPattern {

private static SingletonPattern singletonPattern = null;
private SingletonPattern() {
}
public static SingletonPattern getInstance() {
if (singletonPattern == null) {
singletonPattern = new SingletonPattern();
}
return singletonPattern;
}
}

类加载时只是申明实例,并未实例化,当调用getInstance方法,才进行实例化,
但线程不安全,多个线程并发调用getInstance方法可能会导致创建多份相同的单例出来,
解决的办法就是使用synchronized关键字。

懒汉模式,线程安全

1
2
3
4
5
6
7
8
9
10
public class SingletonPattern {
private static SingletonPattern singletonPattern = null;
public static SingletonPattern getInstance() {
synchronized (SingletonPattern.class) {
if (singletonPattern == null)
singletonPattern = new SingletonPattern();
}
return singletonPattern;
}
}

synchronized保证一个时间内只能有一个线程得到执行,
另一个线程必须等待当前线程执行完才能执行,使得线程安全。
缺点每次调用getInstance方法都进行同步,造成了不必要的同步开销。

饿汉模式

1
2
3
4
5
6
7
8
9
public class SingletonPattern {
//饿汉模式
private static final SingletonPattern singletonPattern = new SingletonPattern();
private SingletonPattern() {
}
public static SingletonPattern getInstance() {
return singletonPattern;
}
}

类加载时就已经进行实例化,类加载较慢,但获取对象速度快,线程安全。

双重校验DCL模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class SingletonPattern {
private static volatile SingletonPattern singletonPattern = null;
private SingletonPattern() {
}
public static SingletonPattern getInstance() {
//第一层校验,为了避免不必要的同步
if (singletonPattern == null) {
synchronized (SingletonPattern.class) {
//第二层校验,实例null的情况下才创建
if (singletonPattern == null)
singletonPattern = new SingletonPattern();
}
}
return singletonPattern;
}
}

这里使用了volatile关键字,因为多个线程并发时初始化成员变量和对象实例化顺序可能会被打乱,
这样就出错了,volatile可以禁止指令重排序。双重校验虽然在一定程度解决了资源的消耗和多余的同步,
线程安全问题,但在某些情况还是会出现双重校验失效问题,即DCL失效。

静态内部类单例模式

1
2
3
4
5
6
7
8
9
10
public class SingletonPattern {
private SingletonPattern() {
}
private static class SingletonPatternHolder {
private static final SingletonPattern singletonPattern = new SingletonPattern();
}
public static SingletonPattern getInstance() {
return SingletonPatternHolder.singletonPattern;
}
}

第一次调用getInstance方法时加载SingletonPatternHolder 并初始化singletonPattern,
这样不仅能确保线程安全,也能保证SingletonPattern类的唯一性。

使用容器

SingletonManager

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

SingletonPattern

1
2
3
4
5
6
7
public class SingletonPattern {
SingletonPattern() {
}
public void doSomething() {
Log.d("wxl", "doSomeing");
}
}

代码调用:

1
2
3
SingletonManager.registerInstance("SingletonPattern", new SingletonPattern());
SingletonPattern singletonPattern = (SingletonPattern) SingletonManager.getInstance("SingletonPattern");
singletonPattern.doSomething();

根据key获取对象对应类型的对象,隐藏了具体实现,降低了耦合度。

枚举单例模式

1
2
3
4
5
6
public enum SingletonEnum {
INSTANCE;
public void doSomething() {
Log.d("wxl", "SingletonEnum doSomeing");
}
}

代码调用:

1
SingletonEnum.INSTANCE.doSomething();

更加简洁,线程安全,还能防止反序列化导致重新创建新的对象,
而以上方法还需提供readResolve方法,防止反序列化一个序列化的实例时,会创建一个新的实例。
枚举单例模式,我们可能使用的不是很多,但《Effective Java》一书推荐此方法,
说“单元素的枚举类型已经成为实现Singleton的最佳方法”。不过Android使用enum之后的dex大小增加很多,
运行时还会产生额外的内存占用,因此官方强烈建议不要在Android程序里面使用到enum,枚举单例缺点也很明显。

Android 应用

AccessibilityManager

1
2
3
4
5
6
7
8
9
10
11
12
13
public final class AccessibilityManager {
static final Object sInstanceSync = new Object();
private static android.view.accessibility.AccessibilityManager sInstance;
public static AccessibilityManager getInstance(Context context) {
synchronized (sInstanceSync) {
if (sInstance == null) {
//省略部分代码
sInstance = new AccessibilityManager(context, null, userId);
}
}
return sInstance;
}
}

InputMethodManager

1
2
3
4
5
6
7
8
9
10
11
12
public final class InputMethodManager {
static android.view.inputmethod.InputMethodManager sInstance;
public static InputMethodManager getInstance() {
synchronized (InputMethodManager.class) {
if (sInstance == null) {
//省略部分代码
sInstance = new InputMethodManager(service, Looper.getMainLooper());
}
return sInstance;
}
}
}

参考

文章目录
  1. 1. 设计模式
    1. 1.1. 创建范例
    2. 1.2. 结构范例
  2. 2. 单例模式
  3. 3. 七种实现方式
    1. 3.1. 懒汉模式,线程不安全
    2. 3.2. 懒汉模式,线程安全
    3. 3.3. 饿汉模式
    4. 3.4. 双重校验DCL模式
    5. 3.5. 静态内部类单例模式
    6. 3.6. 使用容器
    7. 3.7. 枚举单例模式
  4. 4. Android 应用
  5. 5. 参考
|