Design Patterns(一) Singleton

Sometimes it’s important to have only one instance for a class. For example, in a system there should be only one window manager (or only a file system or only a print spooler). Usually singletons are used for centralized management of internal or external resources and they provide a global point of access to themselves.

前言

    单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

饿汉式

    饿汉式(静态常量)
步骤如下:

1) 构造器私有化 (防止 new );
2) 类的内部创建对象;
3) 向外暴露一个静态的公共方法。 getInstance;
4) 代码实现。

优缺点说明:

1) 优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题;
2) 缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费;
3) 这种方式基于classloder机制避免了多线程的同步问题,不过,instance在类装载时就实例化,在单例模式中大多数都是调用getInstance方法, 但是导致类装载的原因有很多种,因此不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance就没有达到lazy loading的效果;
4) 结论:这种单例模式 可用, 可能造成内存浪费。

懒汉式

Double-Check 懒汉式( 线程安全,同步代码块)

步骤如下:

1) 构造器私有化 (防止 new );
2) 类的内部创建对象;
3) 向外暴露一个静态的公共方法。 getInstance;
4) 对getInstance方法进行 Double-Check;
5) 代码实现。

优缺点说明:

1) Double-Check概念是多线程开发中常使用到的,如代码中所示,我们进行了两次if (singleton == null)检查,这样就可以保证线程安全了;
2) 这样,实例化代码只用执行一次,后面再次访问时,判断if (singleton == null),直接return实例化对象,也避免的反复进行方法同步;
3) 线程安全;延迟加载;效率较高;
4) 结论:在实际开发中,推荐使用这种单例设计模式。

静态内部类 懒汉式( 线程安全)

优缺点说明:

1) 这种方式采用了类装载的机制来保证初始化实例时只有一个线程;
2) 静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化;
3) 类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的;
4) 优点:避免了 线程不安全,利用静态内部类特点实现延迟加载,效率高;
5) 结论:推荐使用。

枚举 懒汉式( 线程安全)

1) 这借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象;
2) 这种方式是Effective Java作者Josh Bloch 提倡的方式;
3) 结论:推荐使用。

代码实现

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
/**
* @Auther: Arsenal
* @Date: 2020-03-06 21:50
* @Description: 单例模式
*/
public class Singleton {
public static void main(String[] args) {
EagerSingleton instance1 = EagerSingleton.getInstance();
EagerSingleton instance2 = EagerSingleton.getInstance();
System.out.println("=====饿汉式=====");
System.out.println(instance1 == instance2);
System.out.println(instance1);
System.out.println(instance2);
System.out.println("=====Double-Check懒汉式=====");
LazyDoubleCheckSingleton instance3 = LazyDoubleCheckSingleton.getInstance();
LazyDoubleCheckSingleton instance4 = LazyDoubleCheckSingleton.getInstance();
System.out.println(instance3 == instance4);
System.out.println(instance3);
System.out.println(instance4);
System.out.println("=====静态内部类懒汉式=====");
LazyStaticInnerClassSingleton instance5 = LazyStaticInnerClassSingleton.getInstance();
LazyStaticInnerClassSingleton instance6 = LazyStaticInnerClassSingleton.getInstance();
System.out.println(instance5 == instance6);
System.out.println(instance5);
System.out.println(instance6);
System.out.println("=====枚举懒汉式=====");
LazyEnumSingleton instance7 = LazyEnumSingleton.INSTANCE;
LazyEnumSingleton instance8 = LazyEnumSingleton.INSTANCE;
System.out.println(instance7 == instance8);
System.out.println(instance7);
System.out.println(instance8);
}
}


/**
* 饿汉式(静态常量)应用实例
* 步骤如下:
* 1) 构造器私有化 (防止 new )
* 2) 类的内部创建对象
* 3) 向外暴露一个静态的公共方法。 getInstance
* 4) 代码实现
* <p>
* 优缺点说明:
* 1) 优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同
* 步问题。
* 2) 缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始
* 至终从未使用过这个实例,则会造成内存的浪费
* 3) 这种方式基于classloder机制避免了多线程的同步问题,不过,instance在类装载
* 时就实例化,在单例模式中大多数都是调用getInstance方法, 但是导致类装载
* 的原因有很多种,因此不能确定有其他的方式(或者其他的静态方法)导致类
* 装载,这时候初始化instance就没有达到lazy loading的效果
* 4) 结论:这种单例模式 可用, 可能造成内存浪费
*/
class EagerSingleton {
private final static EagerSingleton eagerSingleton = new EagerSingleton();

private EagerSingleton() {
}

public static EagerSingleton getInstance() {
return eagerSingleton;
}
}

/**
* 优缺点说明:
* 1) Double-Check概念是多线程开发中常使用到的,如代码中所示,我们进行了两
* 次if (singleton == null)检查,这样就可以保证线程安全了。
* 2) 这样,实例化代码只用执行一次,后面再次访问时,判断if (singleton == null),
* 直接return实例化对象,也避免的反复进行方法同步.
* 3) 线程安全;延迟加载;效率较高
* 4) 结论:在实际开发中,推荐使用这种单例设计模式
*/
class LazyDoubleCheckSingleton {
private LazyDoubleCheckSingleton() {
}

private static volatile LazyDoubleCheckSingleton lazySingleton;

public static LazyDoubleCheckSingleton getInstance() {
//双重检查
if (lazySingleton == null) { //Double-Check
synchronized (LazyDoubleCheckSingleton.class) {
if (lazySingleton == null) {
lazySingleton = new LazyDoubleCheckSingleton();
}
}
}
return lazySingleton;
}
}

/**
* 优缺点说明:
* 1) 这种方式采用了类装载的机制来保证初始化实例时只有一个线程。
* 2) 静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化
* 时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的
* 实例化。
* 3) 类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们
* 保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
* 4) 优点:避免了 线程不安全,利用静态内部类特点实现延迟加载,效率高
* 5) 结论:推荐使用.
*/
class LazyStaticInnerClassSingleton {
private LazyStaticInnerClassSingleton() {
}

private static class SingletonInstance {
private static final LazyStaticInnerClassSingleton INSTANCE = new LazyStaticInnerClassSingleton();
}

public static LazyStaticInnerClassSingleton getInstance() {
return SingletonInstance.INSTANCE;
}
}


/**
* 优缺点说明:
* 1) 这借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而
* 且还能防止反序列化重新创建新的对象。
* 2) 这种方式是Effective Java作者Josh Bloch 提倡的方式
* 3) 结论:推荐使用
*/
enum LazyEnumSingleton {
INSTANCE;

public void method() {
}
}

总结

     单例模式注意事项和细节说明

1) 单例模式保证了 系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能;
2) 当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用new;
3) 单例模式使用的场景:需要频繁的进行创建和销毁的对象、创建对象时耗时过多或耗费资源过多(即:重量级对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session工厂等)。

延伸

    单例模式
    设计模式之单例模式
    尚硅谷Java设计模式,韩顺平图解java设计模式

Content
  1. 1. 前言
  2. 2. 饿汉式
  3. 3. 懒汉式
    1. 3.1. Double-Check 懒汉式( 线程安全,同步代码块)
    2. 3.2. 静态内部类 懒汉式( 线程安全)
    3. 3.3. 枚举 懒汉式( 线程安全)
  4. 4. 代码实现
  5. 5. 总结
  6. 6. 延伸