[Java]设计模式之单例模式

什么是设计模式

设计模式(Design 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
25
26
27
28
29

package com.doity.singletonMode;

/**
* Created by ARNO on 2016/4/22/022.
* desc:
* 懒汉模式
* 作用:保证整个应用程序中某个实例有且只有一个
* 区别:饿汉模式的特点是加载类时比较慢,但运行时获取对象的速度比较快,线程安全
* 懒汉模式的特点是加载类时比较快,但运行时获取对象的速度比较慢,线程不安全
*/
public class LazySingleton {
private static final String TAG = "LazySingleton";

//1.将构造方式私有化,不允许外边直接创建对象
private LazySingleton() {
}

//2.声明类的唯一实例,使用 private static 修饰
private static LazySingleton instance;

//3.提供一个用于获取实例的方法,使用 public static 修饰
public static LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
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
package com.doity.singletonMode;

/**
* Created by ARNO on 2016/4/22/022.
* desc:
* 饿汉模式
* 作用:保证整个应用程序中某个实例有且只有一个
* 区别:饿汉模式的特点是加载类时比较慢,但运行时获取对象的速度比较快,线程安全
* 懒汉模式的特点是加载类时比较快,但运行时获取对象的速度比较慢,线程不安全
*/
public class EagerSingleton {
private static final String TAG = "EagerSingleton";

//1. 将构造方法私有化,不允许外部直接创建对象
private EagerSingleton(){
}

//2. 创建类的唯一实例,使用 private static 修饰
private static EagerSingleton instance=new EagerSingleton();

//3.提供一个用于获取实例的方法,使用 public static 修饰
public static EagerSingleton getInstance(){
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
package com.doity.singletonMode;

/**
* Created by ARNO on 2016/4/22/022.
* desc:
*/
public class TestSingleton {
private static final String TAG = "TestSingleton";

public static void main(String[] args) {

//饿汉模式
LazySingleton l1 = LazySingleton.getInstance();
LazySingleton l2 = LazySingleton.getInstance();
if (l1 == l2) {
System.out.println("l1 与 l2 是同一个实例");
} else {
System.out.println("l1 与 l2 不是同一个实例");
}

//懒汉模式
EagerSingleton e1 = EagerSingleton.getInstance();
EagerSingleton e2 = EagerSingleton.getInstance();
if (e1 == e2) {
System.out.println("e1 与 e2 是同一个实例");
} else {
System.out.println("e1 与 e2 不是同一个实例");
}
}
}

测试结果

1
2
3
4

l1 与 l2 是同一个实例

e1 与 e2 是同一个实例

线程安全的问题

饿汉式天生就是线程安全的,可以直接用于多线程而不会出现问题,

懒汉式本身是非线程安全的,为了实现线程安全有多种写法。

Singleton 通过将构造方法限定为 private 避免了类在外部被实例化,在同一个虚拟机范围内,Singleton 的唯一实例只能通过 getInstance() 方法访问。(事实上,通过 Java 反射机制是能够实例化构造方法为 private 的类的,那基本上会使所有的 Java 单例实现失效。此问题在此处不做讨论,姑且掩耳盗铃地认为反射机制不存在。)

但是以上实现没有考虑线程安全问题。所谓线程安全是指:如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。或者说:一个类或者程序所提供的接口对于线程来说是原子操作或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题。显然以上实现并不满足线程安全的要求,在并发环境下很可能出现多个 Singleton 实例。

结论

单例模式为一个面向对象的应用程序提供了对象惟一的访问点,不管它实现何种功能,整个应用程序都会同享一个实例对象!区别在于线程安全,资源加载的时机。

参考资料