设计模式笔记 – 单例模式(Singleton Patttern)

模式定义

单例模式(Singleton Pattern)确保某一个类只有一个实例,而且自行实例化并向整个系统提供该唯一实例。这个类称为单例类,它提供全局访问的方法。

单例模式有三个要点:

  • 某个类中有一个实例
  • 它必须自行创建这个实例
  • 它必须自行向整个系统提供这个实例

模式结构

单例模式包含如下角色:

  • Singleton:单例

img


模式实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Singleton - 单例
public class Singleton {
private static Singleton singleton;

// 设置Singleton的构造器为私有,即外部不可实例化该类
private Singleton() {
;
}

// 这是其中一种实现方式,但并不能保证在多线程情况下安全
public static Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}

return singleton;
}
}

模式分析

  • 单例模式的目的是保证一个类仅有一个实例,并提供一个访问它的全局访问点。单例模式包含的角色有且只有一个。
  • 单例模式拥有一个私有构造函数,确保用户无法通过new关键字直接实例化它。除此之外,该模式中包含一个静态私有成员变量与静态公有的工厂方法,该工厂方法负责检验实例的存在性并实例化自己,然后存储在静态成员变量中,以确保只有一个实例被创建。

单例模式的优点:

  • 提供了对唯一实例的受控访问。因为单例类封装了它的唯一实例,所以它可以严格控制客户怎样或何时访问它,并为设计及开发团队提供了共享的概念。
  • 由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象,单例模式无疑提高了系统的性能。
  • 允许可变数目的实例。可以基于单例模式的思想进行扩展,使用与单例控制相似的方法来获取指定数目的实例。

单例模式的缺点:

  • 单例类不能被继承(因为单例类的私有构造器对外不可见),也无法继承(因为继承,则必须确保父类也是单例类,才能确保一致性,但父类也是私有构造器,对子类不可见),对单例类的扩展有很大的难度。
  • 单例类的职责过重,在一定程度上违背了类应遵循的“单一职责原则”。因为单例类即充当了工厂角色,提供工厂方法,同时也充当了产品角色,包含一些业务方法,将产品的创建和产品的本身的功能耦合在一起。

传统实现方式引发的多线程问题:

在多线程问题上,单例模式中传统的实现方式会在多个线程第一次同时访问创建实例的方法时,产生多个对象。

线程A调用getInstance(),判断singleton为空并创建Singleton对象,同时线程B也调用getInstance(),恰巧也判断singleton为空,继而创建了第二个Singleton对象。这样一来延伸至若干个线程也这么做,后果不堪设想。

以下有不同的解决方式,各有优缺点:

  • 在单例类中静态变量中直接赋值,确保每次获取该类的实例中只有一个。但是这样就无法做到按需加载的目的,容易造成浪费资源。
1
2
3
4
5
6
7
8
9
10
11
public class Singleton {
private static Singleton singleton = new Singleton();

private Singleton() {
;
}

public static Singleton getInstance() {
return singleton;
}
}
  • 在单例类的传统的获取实例方法中,增加synchronized同步块。虽能做到同步加载,但每次获取该实例,都需要进行同步检查,性能会有所下降。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Singleton {
private static Singleton singleton;

private Singleton() {
;
}

public static synchronized Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}

return singleton;
}
}
  • 在单例类中的静态变量添加volatile关键字,并使用“双重检查加锁”机制。只有在对象不存在的时候使用同步。
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
public class Singleton {
/*
* 当把变量声明为volatile类型后,编译器与运行时都会注意到这个变量是
* 共享的,因此不会将该变量上的操作与其他内存操作一起重排序。
* volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此
* 在读取volatile类型的变量时总会返回最新写入的值。
*/
private volatile static Singleton singleton;

private Singleton() {
;
}

public static Singleton getInstance() {

// 只有第一次才彻底往下执行
if (singleton == null) {

// 执行此同步块时,确保线程安全
synchronized (Singleton.class) {

/* 当有多个线程同时访问时,会进行排队,若不进行二次判
* 断,当第一个线程访问结束已创建对象时,第二个线程紧接
* 着访问,仍会再次创建对象,不符合单例模式的思想。
*/
if (singleton == null) {
singleton == new Singleton();
}
}
}

return singleton;
}
}


参考资料:

[1] https://design-patterns.readthedocs.io/zh_CN/latest/creational_patterns/singleton.html

[2] http://www.cnblogs.com/zhengbin/p/5654805.html