保证一个类仅有一个实例,并提供一个访问它的全局访问点。
要想控制一个类只被创建一个实例,那么首要的问题就是把创建实例的权限收回来,让类自己来负责创建自己类的实例,然后由这个类来提供外部可以访问这个类实例的方法,这就是单例模式的实现方法。
单例模式的结构和实现
Singleton:负责创建Singleton类自己的唯一实例,并提供一个getInstance的方法,来外部来访问这个类的唯一实例。
- 私有化构造方法
- 提供静态的获取实例的方法
- 定义存储实例的属性,因为要在静态方法中使用,因此要加上static修饰
- 实现控制实例的创建
示例代码
懒汉模式
所谓懒汉模式,既然懒,那么在创建对象实例时就不要着急,在马上要使用对象实例时才会创建,在装载对象时不会创建对象实例。
1 | package com.liuhao.designpattern.singleton; |
饿汉模式
所谓饿汉模式,就是在创建对象实例时比较急,在装载类的时候就会创建对象实例。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
42package com.liuhao.designpattern.singleton;
/**
* 单例模式饿汉模式
*
* @author liuhao
*
* 2015年7月17日
*/
public class SingletonHungry {
// 4. 直装载类的时候就会创建对象实例,只创建一次
private static SingletonHungry instance = new SingletonHungry();
// 1. 构造方法私有化,好在内部控制创建实例的数目
private SingletonHungry() {
}
// 2. 定义一个方法为客户端提供类实例
// 3. 这个方法需要定义成类方法,也就是
public static SingletonHungry getInstance() {
// 5. 直接使用以及创建好的实例
return instance;
}
private String singletonData;// 单例可以有自己的属性
/**
* 获取属性的值
*
* @return 属性的值
*/
public String getSingletonData() {
return singletonData;
}
/**
* 单例可以有自己的操作方法
*/
public void singletonOperation() {
}
}
其他
单例模式的范围
目前Java实现的单例是一个虚拟机范围的,因为装载类的功能是虚拟机的,所以一个虚拟机在通过自己的ClassLoader装载饿汉模式实现单例时就会创建一个类的实例。
因此如果一个虚拟机里面有很多个ClassLoader,而这些ClassLoader都装载某个类的话,就会产生多个实例。
单例模式的调用顺序
- 懒汉模式
- 饿汉模式
体现的一些思想
延迟加载
懒汉模式中,一开始没有加载所需的资源或者数据,一直等到马上就要使用了才加载,即所谓的“延迟加载”。缓存
当某些资源或数据被频繁的使用,而且它们是存储在系统外部的(如数据库、硬盘),那么每次操作都要从数据库获取,速度会很慢,操作性能问题。
一个简单的方法就是把这些数据缓存到内存中,每次操作的时候先到内存里找,若有则直接使用;若果没有再获取它并设置到缓存中。
缓存是一种典型的空间换取时间的方案。
线程安全
不加同步的懒汉模式是线程不安全的。比如,有线程A、B同时调用getInstance方法,那就可能导致并发问题,如图。
如何实现懒汉模式的线程安全
加上关键字
synchronized
,如下1
public static synchronized SingletonLazy getInstance() {}
双重检查加锁。不是在每次进入getInstance时都需要同步,而是先不同步,进入方法后,先检查实例是否存在。如果不存在才进入下面的同步块,这是第一重检查。进入同步块后,再次检查实例是否存在,如果不存在,就在同步的情况下创建一个实例,这是第二重检查。
双重检查加锁机制的实现需要使用volatile
关键字,被它修饰的变量的值将不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存,从而确保多个线程能够正确的处理该变量。
具体实现:1
2
3
4
5
6
7
8
9
10
11
12
13public static SingletonVolatile getInstance() {
// 检查实例是否存在,不存在则进入到同步块
if (instance == null) {
// 同步块,线程安全地创建实例
synchronized (SingletonVolatile.class) {
// 再次检查实例是否存在,不存在则真正的创建实例
if (instance == null) {
instance = new SingletonVolatile();
}
}
}
return instance;
}这种实现方式既可以实现线程安全,同时也不会对性能造成太大的影响。
- 饿汉模式是线程安全的。