alexpdh's blog

Java设计模式(十八):单例模式

单例模式(singleton pattern)

单例模式(singleton pattern):属于对象的创建型模式。保证一个类仅有一个实例,并提供一个访问它的全局访问点。


单例模式UML图

singleton-pattern


单例模式特点

  • 单例类只能实例化一次。
  • 单例类需要自己创建自己的唯一实例。
  • 单例类需要提供一个给其它对象访问该唯一实例的方法。

示例代码

饿汉式单例模式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.example.singletonPattern;
/**
* 单例模式:饿汉式
*
* @author pengdh
* @date: 2017-07-30 1:58
*/
public class EagerSingleton {
private static EagerSingleton instance = new EagerSingleton();
/**
* 私有构造器
*/
private EagerSingleton(){}
/**
* 静态工厂方法,获取全局唯一实例
* @return
*/
public static EagerSingleton getInstance () {
return instance;
}
}

如上示例中,当类第一次加载时就会初始化静态变量 instance ,此时就会调用类的私有构造器,这时就创建出了单例类的唯一实例。饿汉式是典型的空间换时间,当类装载的时候就会创建类的实例,不管你用不用,先创建出来,然后每次调用的时候,就不需要再判断,节省了运行时间。

懒汉式单例模式
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
package com.example.singletonPattern;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 单例模式:懒汉式
*
* @author pengdh
* @date: 2017-07-30 2:08
*/
public class LazySingleton {
private static LazySingleton instance = null;
/**
* 私有构造器
*/
private LazySingleton() {}
/**
* 静态工厂方法,获取全局唯一实例
* @return
*/
public static LazySingleton getInstance() {
Lock lock = new ReentrantLock();
lock.lock();
try {
if (instance == null) {
instance = new LazySingleton();
}
} finally {
lock.unlock();
}
return instance;
}
}

如上示例中,当单例类加载时不会立刻创建对象的实例,而是在调用类的静态工厂方法时才创建对象的唯一实例,我们在获取实例的静态方法中使用了锁,这样做的目的是防止在多线程环境下多个对象同时调用静态工厂方法时出现并发问题。懒汉式是典型的时间换空间,就是每次获取实例都会进行判断,看是否需要创建实例,浪费判断的时间。当然,如果一直没有人使用的话,那就不会创建实例,则节约内存空间。

双重检查加锁
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
package com.example.singletonPattern;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 单例模式:双重检查加锁
*
* @author pengdh
* @date: 2017-07-30 2:36
*/
public class DoubleCheckSingleton {
private volatile static DoubleCheckSingleton instance = null;
/**
* 私有构造器
*/
private DoubleCheckSingleton() {}
/**
* 静态工厂方法,获取全局唯一实例
* @return
*/
public static DoubleCheckSingleton getInstance() {
// 先检查实例是否存在,如果不存在才进入下面的同步块
if (instance == null) {
// 加锁,线程安全的创建实例
Lock lock = new ReentrantLock();
lock.lock();
try {
// 再次检查实例是否存在,如果不存在才真正的创建实例
if (instance == null) {
instance = new DoubleCheckSingleton();
}
} finally {
lock.unlock();
}
}
return instance;
}
}

如上示例使用了“双重检查加锁”的方式实现,既实现线程安全,又能够使性能不受很大的影响。所谓“双重检查加锁”机制,指的是:并不是每次进入getInstance方法都需要同步,而是先不同步,进入方法后,先检查实例是否存在,如果不存在才进行下面的同步块,这是第一重检查,进入同步块过后,再次检查实例是否存在,如果不存在,就在同步的情况下创建一个实例,这是第二重检查。这样一来,就只需要同步一次了,从而减少了多次在同步情况下进行判断所浪费的时间。

Lazy initialization holder class模式
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
package com.example.singletonPattern;
/**
* 单例模式:Lazy initialization holder class模式
*
* @author pengdh
* @date: 2017-07-30 2:46
*/
public class Singleton {
/**
* 私有构造器
*/
private Singleton() {
}
/**
* 类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例
* 没有绑定关系,而且只有被调用到时才会装载,从而实现了延迟加载。
*/
private static class SingletonHolder {
/**
* 静态初始化器,由JVM来保证线程安全
*/
private static Singleton instance = new Singleton();
}
/**
* 静态工厂方法,获取全局唯一实例
*/
public static Singleton getInstance() {
return SingletonHolder.instance;
}
}

如上示例中,当getInstance方法第一次被调用的时候,它第一次读取SingletonHolder.instance,导致 SingletonHolder 类得到初始化;而这个类在装载并被初始化的时候,会初始化它的静态域,从而创建 LazyHolderSingleton 的实例,由于是静态的域,因此只会在虚拟机装载类的时候初始化一次,并由虚拟机来保证它的线程安全性。这个模式的优势在于,getInstance方法并没有被同步,并且只是执行一个域的访问,因此延迟初始化并没有增加任何访问成本。

单例和枚举
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.example.singletonPattern;
/**
* 单例模式:使用枚举来实现单例模式
*
* @author pengdh
* @date: 2017-07-30 2:58
*/
public enum SingletonEnum {
/**
* 定义一个枚举的元素,它就代表了 SingletonEnum 的一个实例。
*/
uniqueInstance;
/**
* 单例可以有自己的操作
*/
public void singletonOperation() {
// 功能实现
}
}

使用枚举来实现单实例控制会更加简洁,而且无偿地提供了序列化机制,并由JVM从根本上提供保障,绝对防止多次实例化,是更简洁、高效、安全的实现单例的方式。

客户端测试类
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
package com.example.singletonPattern;
/**
* 单例模式:客户端测试类
*
* @author pengdh
* @date: 2017-07-30 3:20
*/
public class SingletonTest {
public static void main(String[] args) {
EagerSingleton eager1 = EagerSingleton.getInstance();
EagerSingleton eager2 = EagerSingleton.getInstance();
LazySingleton lazy1 = LazySingleton.getInstance();
LazySingleton lazy2 = LazySingleton.getInstance();
DoubleCheckSingleton double1 = DoubleCheckSingleton.getInstance();
DoubleCheckSingleton double2 = DoubleCheckSingleton.getInstance();
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
if (eager1 == eager2) {
System.out.println("\"饿汉式单例模式\" 的两个对象是相等的!");
}
if (lazy1 == lazy2) {
System.out.println("\"懒汉式单例模式\" 的两个对象是相等的!");
}
if (double1 == double2) {
System.out.println("\"双重检查加锁模式单例模式\" 的两个对象是相等的!");
}
if (s1 == s2) {
System.out.println("\"Lazy initialization holder class模式单例模式\" 的两个对象是相等的!");
}
}
}

参考文献

alexpdh wechat
欢迎扫一扫关注 程序猿pdh 公众号!