1. 定义

Ensure a class has only one instance, and provide a global point of access to it.
确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

2. 要点

  • 只创建一次
  • 通常将构造器私有化
  • 需注意线程安全
  • 懒加载(lazy loading)
  • 一般不考虑反射(Java反射可以实例化private)

3. 单例模式的种类

3.1 饿汉模式

  • 3.1.1 公有的静态final域
    1
    2
    3
    4
    5
    6
    7
    8
    public class Singleton_Hungry extends BasePattern {

    public static final Singleton_Hungry singleton = new Singleton_Hungry();

    private Singleton_Hungry() {
    }

    }

分析

  1. 够简单粗暴
  2. 代码清晰表明是个单例
  3. 无lazy loading
  • 3.1.2 静态工厂实现
1
2
3
4
5
6
7
8
9
10
11
public class Singleton_Hungry extends BasePattern {

private static final Singleton_Hungry singleton = new Singleton_Hungry();

private Singleton_Hungry() {
}

public static Singleton_Hungry getInstance() {
return singleton;
}
}

分析:

  1. 线程安全(使用ClassLoader机制避免了多线程同步)
  2. 没有实现懒加载(lazy loading),将类的创建委托给了类装载器
  3. 主要在类加载时,就自行初始化了
  4. 也可以在static块中进行初始化
  5. 静态工厂实现,有更大灵活性

3.2 懒汉模式

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Singleton_Lazy extends BasePattern {

private static Singleton_Lazy singleton = null;

private Singleton_Lazy() {
}

public static Singleton_Lazy getInstance() {
if (singleton == null) singleton = new Singleton_Lazy();
return singleton;
}

}

分析:

  1. Lazy Loading
  2. 并发下,线程不安全,有可能重复创建对象
  3. 不推荐在生产中使用

3.3 改进式饿汉模式

  • 方式一:
1
2
3
4
5
6
7
8
9
10
11
12
public class Singleton_Lazy_Sync {

private static Singleton_Lazy_Sync singleton = null;

private Singleton_Lazy_Sync() {
}

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

备注: synchronized 确保只有一个线程访问

分析

  1. 保证了getInstance()的并发情况下的,只能有一个线程访问
  2. 效率过低,每次都要确保同步的情况下获取实例
  3. 不推荐使用
  • 方式二:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Singleton_Lazy_Sync {

private static Singleton_Lazy_Sync singleton = null;

private Singleton_Lazy_Sync() {
}

public static Singleton_Lazy_Sync getInstance() {
synchronized(Singleton_Lazy_Sync.class){
if (singleton == null) singleton = new Singleton_Lazy_Sync();
}
return singleton;
}
}

分析

  1. 加synchronized确保并发情况下能其为同一时刻只能有一个线程能够访问
  2. 对Singleton_Lazy_Sync类加同步关键字,效率同样偏低

3.4 双重校验锁方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Singleton_DoubleCheck {

private volatile static Singleton_DoubleCheck singleton;

private Singleton_DoubleCheck() {
}

public static Singleton_DoubleCheck getInstance() {
if (singleton == null) {
synchronized (Singleton_DoubleCheck.class) {
if (singleton == null) {
singleton = new Singleton_DoubleCheck();
}
}
}
return singleton;
}
}

备注:Volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。

分析

  1. 单例仅new一次,在synchronized 加上判空语句,仅执行一次同步锁定,其余的直接跳过
  2. 是基本懒汉式的改进型
  3. volatile 确保变量值直接从内存读取(多个线程中存在副本),另,该特性java 1.5版本之后才有用
  4. volatile 会禁止指令重新排序
  5. 实现有点复杂

3.5 静态内部类

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

private static class SingletonHolder {
private static Singleton_InnerClass singleton = new Singleton_InnerClass();
}

private Singleton_InnerClass() {
}

private static final Singleton_InnerClass getInstance() {
return SingletonHolder.singleton;
}
}

分析

  1. 同样利用了classloder的机制来保证初始化instance时只有一个线程
  2. 静态内部类只会加一次,故线程安全
  3. 外部类能直接访问内部类(不管是否是静态的)的私有变量
  4. 内部类SingletonHolder只有在getInstance()方法第一次调用的时候才会被加载(实现了lazy),而且其加载过程是线程安全的(实现线程安全)。内部类加载的时候仅实例化一次instance。

3.6 枚举

1
2
3
public enum Singleton {
INSTANCE;
}

分析

  1. 简洁优雅
  2. 无偿提供了序列化机制,绝对防止多次实例化,即使使用复杂的序列化或反射攻击
  3. 单元素的枚举类型
  4. 枚举实例的创建默认就是线程安全

4 单例模式扩展

4.1 固定数量实例

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
35
36
37
38
39
40
public class Singleton_FixedNum {

// 产生的最大实例数
private static int MAX_NUM = 30;

// 存放实例集合
private static List<Singleton_FixedNum> list = new ArrayList<>();

private static Random random = new Random();

// 类加载时,初始化到最大
static {
for (int i = 0; i < MAX_NUM; i++) {
list.add(new Singleton_FixedNum(i));
}
}

private int num;

public int getNum() {
return num;
}

public void setNum(int num) {
this.num = num;
}

private Singleton_FixedNum() {
}


private Singleton_FixedNum(int num) {
this.num = num;
}

public static Singleton_FixedNum getInstance() {
return list.get(random.nextInt(MAX_NUM));
}

}

分析

  1. 单例的一种扩展,限定产生的实例的数量
  2. 也可以创建动态数量的实例,如线程池

4.2 登记式单例模式

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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// 父类
public class Singleton_Reg {

private static HashMap m_reg = new HashMap<>();

static {
Singleton_Reg singleton_reg = new Singleton_Reg();
m_reg.put(singleton_reg.getClass().getName(), singleton_reg);
}

protected Singleton_Reg() {
}

public static Singleton_Reg getInstance(String name) {
if (name == null) {
name = Singleton_Reg.class.getName();
}
if (m_reg.get(name) == null) {
try {
m_reg.put(name, (Singleton_Reg) Class.forName(name).newInstance());
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
return m_reg.get(name);
}

}

// 子类
public class Singleton_RegChild extends Singleton_Reg {

public Singleton_RegChild() {
}


// 静态工厂
public static Singleton_RegChild getInstance() {
return (Singleton_RegChild) Singleton_Reg.getInstance(Singleton_RegChild.class.getName());
}

public static void main(String[] args) {
Singleton_RegChild.getInstance();
}
}

分析

  1. 登记式单例类是Gof为了克服饿汉式和懒汉式单例均不可继承而设计的
  2. 子类实现只能是懒汉式
  3. 缺点: (1)由于子类必须允许父类以构造器调用产生实例,因此,
    它的构造方法必须是公开的这样一来,就等于允许了以这样方式产生实例
    而不在父类的登 记中。(2)由于父类的实例必须存在才可能有子类的实例,
    这在有些情况下是一个浪费。这是登记式单例类的另一个缺点

5. 优点

  1. 提供唯一个实例的受控访问,减少内存开支,减少系统性能的开销
  2. 避免对资源的多重占用
  3. 可以设置全局访问点,优化和共享资源访问
  4. 允许可变数目的实例

6. 缺点

  1. 单例模式没有接口,难以扩展
  2. 单例类的职责过重,一定程度上违背了“单一职责原则”

7. 应用场景

  1. 项目中需要一个共享访问点或共享数据
  2. 创建一个对象需要消耗的资源过多,如要访问IO
  3. 需要定义大量的静态常量和静态方法(也可以直接使用static)

8. 引用

  • 书籍

(秦小波)《设计模式之禅》第7章
(Joshua Bloch)《Effective Java》 第2章 第3条