我学设计模式:单例(Singleton)模式

保证一个类仅有一个实例,并提供一个访问它的全局访问点。

要想控制一个类只被创建一个实例,那么首要的问题就是把创建实例的权限收回来,让类自己来负责创建自己类的实例,然后由这个类来提供外部可以访问这个类实例的方法,这就是单例模式的实现方法。

单例模式的结构和实现

单例模式的结构图

Singleton:负责创建Singleton类自己的唯一实例,并提供一个getInstance的方法,来外部来访问这个类的唯一实例。

  1. 私有化构造方法
  2. 提供静态的获取实例的方法
  3. 定义存储实例的属性,因为要在静态方法中使用,因此要加上static修饰
  4. 实现控制实例的创建

示例代码

懒汉模式

所谓懒汉模式,既然懒,那么在创建对象实例时就不要着急,在马上要使用对象实例时才会创建,在装载对象时不会创建对象实例。

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
package com.liuhao.designpattern.singleton;

/**
* 单例模式懒汉模式
*
* @author liuhao
*
* 2015年7月17日
*/
public class SingletonLazy {
// 4. 定义一个变量来存储创建好的类实例
// 5. 因为要在静态方法中使用,因此要加上static修饰
private static SingletonLazy instance = null;

// 1. 构造方法私有化,好在内部控制创建实例的数目
private SingletonLazy() {
}

// 2. 定义一个方法为客户端提供类实例
// 3. 这个方法需要定义成类方法,也就是
public static SingletonLazy getInstance() {
// 6. 判断存储实例的变量是否有值
if (instance == null) {
instance = new SingletonLazy();
}
return instance;
}

private String singletonData;// 单例可以有自己的属性

/**
* 获取属性的值
*
* @return 属性的值
*/
public String getSingletonData() {
return singletonData;
}

/**
* 单例可以有自己的操作方法
*/
public void singletonOperation() {

}
}

饿汉模式

所谓饿汉模式,就是在创建对象实例时比较急,在装载类的时候就会创建对象实例。

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
package 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都装载某个类的话,就会产生多个实例。

单例模式的调用顺序

  • 懒汉模式

单例模式的调用顺序

  • 饿汉模式

单例模式的调用顺序

体现的一些思想

  • 延迟加载
    懒汉模式中,一开始没有加载所需的资源或者数据,一直等到马上就要使用了才加载,即所谓的“延迟加载”。

  • 缓存
    当某些资源或数据被频繁的使用,而且它们是存储在系统外部的(如数据库、硬盘),那么每次操作都要从数据库获取,速度会很慢,操作性能问题。

一个简单的方法就是把这些数据缓存到内存中,每次操作的时候先到内存里找,若有则直接使用;若果没有再获取它并设置到缓存中。

缓存是一种典型的空间换取时间的方案。

线程安全

  1. 不加同步的懒汉模式是线程不安全的。比如,有线程A、B同时调用getInstance方法,那就可能导致并发问题,如图。
    懒汉模式线程冲突

  2. 如何实现懒汉模式的线程安全

    1. 加上关键字synchronized,如下

      1
      public static synchronized SingletonLazy getInstance() {}
    2. 双重检查加锁。不是在每次进入getInstance时都需要同步,而是先不同步,进入方法后,先检查实例是否存在。如果不存在才进入下面的同步块,这是第一重检查。进入同步块后,再次检查实例是否存在,如果不存在,就在同步的情况下创建一个实例,这是第二重检查。
      双重检查加锁机制的实现需要使用volatile关键字,被它修饰的变量的值将不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存,从而确保多个线程能够正确的处理该变量。
      具体实现:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      public static SingletonVolatile getInstance() {
      // 检查实例是否存在,不存在则进入到同步块
      if (instance == null) {
      // 同步块,线程安全地创建实例
      synchronized (SingletonVolatile.class) {
      // 再次检查实例是否存在,不存在则真正的创建实例
      if (instance == null) {
      instance = new SingletonVolatile();
      }
      }
      }
      return instance;
      }

      这种实现方式既可以实现线程安全,同时也不会对性能造成太大的影响。

  3. 饿汉模式是线程安全的。
hoxis wechat
一个脱离了高级趣味的程序员,关注回复1024有惊喜~
赞赏一杯咖啡
0%