Java 设计模式——单例模式

一、单例模式的介绍

1.1 什么是单例模式

单例模式指的是一个类只会有一个实例,即一个类只有一个对象实例。它的特点有:

  • 单例类只能有一个实例

  • 单例类必须自己创建自己的唯一实例

  • 单例类必须给所有其他对象提供这一实例

1.2 单例模式的应用场景

(1)一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务;售票时,一共有100张票,可有有多个窗口同时售票,但需要保证不要超售(这里的票数余量就是单例,售票涉及到多线程)。

(2)在前端创建工具箱窗口,工具箱要么不出现,出现也只出现一个。

遇到问题:每次点击菜单都会重复创建“工具箱”窗口。

解决方案:使用 if 语句,在每次创建对象的时候首先进行判断是否为 null ,如果为 null 再创建对象。

(3)如果在5个地方需要实例出工具箱窗体。

遇到问题:这个小 bug 需要改动5个地方,并且代码重复,代码利用率低

解决方案:利用单例模式,保证一个类只有一个实例,并提供一个访问它的全局访问点。

1.3 唯一实例为什么是static

单例模式实现过程如下:

  1. 首先,将该类的构造函数私有化(目的是禁止其他程序创建该类的对象);

  2. 其次,在本类中自定义一个对象(既然禁止其他程序创建该类的对象,就要自己创建一个供程序使用,否则类就没法用,更不是单例);

  3. 最后,提供一个可访问类自定义对象的类成员方法(对外提供该对象的访问方式)。

直白的讲就是,你不能用该类在其他地方创建对象,而是通过该类自身提供的方法访问类中的那个自定义对象。那么问题的关键来了,程序调用类中方法只有两种方式:

  • 创建类的一个对象,用该对象去调用类中方法;

  • 使用类名直接调用类中方法,格式“类名.方法名()”;

上面说了,构造函数私有化后第一种情况就不能用,只能使用第二种方法。

使用类名直接调用类中方法,类中方法必须是静态的,而静态方法不能访问非静态成员变量,因此类自定义的实例变量也必须是静态的。

1.4 双重校验锁实现为什么要加 volatile

先说结论,作用主要有两个:

  1. 保证该变量在多线程下的可见性

  2. 限制编译器指令重排

第一点就不解释了,关于 volatile 详细介绍可以看这篇文章:Java并发编程——volatile关键字解析

关于第二点,首先解释下编译器的指令重排序,一般来说,处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。

1
2
3
4
int a = 10;    //语句1
int r = 2; //语句2
a = a + 3; //语句3
r = a * a; //语句4

这段代码有4个语句,那么可能的一个执行顺序是: 语句2—>语句1—>语句3—>语句4。但是不可能是: 语句2—>语句1—>语句4—> 语句3。

因为处理器在进行重排序时是会考虑指令之间的数据依赖性,如果一个指令Instruction 2必须用到Instruction 1的结果,那么处理器会保证Instruction 1会在Instruction 2之前执行。虽然重排序不会影响单个线程内程序执行的结果,但是多线程下就有可能出现问题,例如:

1
2
3
4
5
6
7
8
9
//线程1:
context = loadContext(); //语句1
inited = true; //语句2

//线程2:
while(!inited ){
sleep()
}
doSomethingwithconfig(context);

上面代码中,由于语句1和语句2没有数据依赖性,因此可能会被重排序。假如发生了重排序,在线程1执行过程中先执行语句2,而此是线程2会以为初始化工作已经完成,那么就会跳出while循环,去执行doSomethingwithconfig(context)方法,而此时context并没有被初始化,就会导致程序出错。

介绍完指令重排序,下面言归正传:

对于Java编译器而言,初始化实例和将对象地址写到字段中并非是原子操作,且这两个阶段的执行顺序是未定义的。假设某个线程执行了 new Singleton(),构造方法还未被调用,编译器仅仅为该对象分配了内存空间并设定默认值,此时若其他线程调用 getInstance() 方法,由于 instance != null,但是此时 instance 对象还没有被赋予真正的有效值,从而无法取到正确的单例对象。

使用 volatile 关键字限制编译器对它的相关读写操作,对它的读写操作进行指令重排,确定对象实例化后才返回引用。

二、单例模式的实现

单例模式可以分为懒汉式饿汉式

  • 懒汉式单例模式:在类加载时不初始化。

  • 饿汉式单例模式:在类加载时就完成了初始化,所以类加载比较慢,但获取对象的速度快。

2.1 懒汉(线程不安全)

1
2
3
4
5
6
7
8
9
10
11
12
class Singleton {
private static Singleton instance;

private Singleton() {}

public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}

这种写法是懒加载很明显,但是在多线程不能正常工作。 

2.2 懒汉(线程安全)

1
2
3
4
5
6
7
8
9
10
11
12
class Singleton {
private static Singleton instance;

private Singleton() {}

public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}

这种写法在getInstance()方法中加入了synchronized锁。能够在多线程中很好的工作,而且也具备很好的懒加载,但是效率很低(因为锁),并且大多数情况下不需要同步。

2.3 恶汉

1
2
3
4
5
6
7
8
9
class Singleton {
private static Singleton instance = new Singleton();

private Singleton() {}

public static Singleton getInstance() {
return instance;
}
}

这种方式基于ClassLoder机制避免了多线程的同步问题,不过instance在类装载时就实例化,这时候初始化instance显然没有达到懒加载的效果

2.4 恶汉变种

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Singleton {
private static Singleton instance = null;

static {
instance = new Singleton();
}

private Singleton() {}

public static Singleton getInstance() {
return instance;
}
}

2.5 静态内部类

1
2
3
4
5
6
7
8
9
10
11
class Singleton {
private static class SingletonHolder {
private static Singleton instance = new Singleton();
}

private Singleton() {}

public static Singleton getInstance() {
return SingletonHolder.instance;
}
}

这种方式同样利用了ClassLoder的机制来保证初始化instance时只有一个线程,但是恶汉模式只要Singleton类被装载了,那么instance就会被实例化,而这种方式只有显示通过调用getInstance()方法时,才会显示装载SingletonHolder类,从而实例化instance,从而达到懒加载的效果。

2.6 双重校验锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Singleton {
private volatile static Singleton singleton;

private Singleton() {}

public static Singleton getInsatance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}

这是线程安全版懒汉的升级版,在JDK1.5之后,使用双重检查锁定才能够正常达到单例效果。


参考资料:

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×