单例模式

Author Avatar
xuanzh.cc 11月 12, 2017
  • 在其它设备中阅读本文章

定义

一个类有且仅有一个实例,并且自行实例化想整个系统提供。

要点:

  1. 构造方法私有化
  2. 多线程环境下要确保实例的唯一性
  3. 使用一个静态方法为外部提供该实例的访问

实现方式(5个):

1、懒汉式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 懒汉式
*/
public class SingletonLazy {
//静态实例
private static SingletonLazy instance = null;
/**
* 构造方法私有化
*/
private SingletonLazy(){}
/**
* 单例获取方法
* @return
*/
public static synchronized SingletonLazy getInstance(){
if(instance == null) {
instance = new SingletonLazy();
}
return instance;
}
}

该方法可以延迟加载,但是由于加了 synchronized,会导致性能下降。

2、饿汉式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 饿汉式
*/
public class SingletonHungry {
//静态实例
private static SingletonHungry instance = new SingletonHungry();
/**
* 构造方法私有化
*/
private SingletonHungry(){}
/**
* 单例获取方法
* @return
*/
public static SingletonHungry getInstance(){
return instance;
}
}

该方法使用了类加载的机制来避免了多线程的同步问题,但是没有延迟初始化(lazy-init)。

3、双重所校验(方式1的升级版)

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
public class SingletonDoubleCheck {
//静态实例
private static SingletonDoubleCheck instance = null;
/**
* 构造方法私有化
*/
private SingletonDoubleCheck(){}
/**
* 单例获取方法
* @return
*/
public static SingletonDoubleCheck getInstance(){
if (instance == null) {
//1 这里可能会因为并发问题,而同时执行到这里
synchronized (SingletonDoubleCheck.class) {
//如果一个线程先执行到了这里,那么下次另一个线程执行到了位置1后,也会执行到这里,所以这里再加一次判断库避免之前被new 出来的实例被覆盖。
if (instance == null) {
instance = new SingletonDoubleCheck();
}
return instance;
}
}
return instance;
}
}

注:在jdk1.5以后才支持(jdk1.5 开始采用新的内存模型,不会因为指令重排而使双重所检查失效。happen-before)

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
/**
* 静态内部类
*/
public class SingletonInnerClass {
/**
* 构造方法私有化
*/
private SingletonInnerClass(){}
/**
* 单例获取方法
* @return
*/
public static SingletonInnerClass getInstance(){
return InnerHolder.instance;
}
/**
* 静态内部类
*/
private static class InnerHolder {
private static SingletonInnerClass instance = new SingletonInnerClass();
}
}

该方法即可以保证线程安全,也支持 lazy-init。 (推荐使用这个方法)

5、枚举

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 枚举方式
*/
public enum SingletonEnum {
INSTANCE ,
;
/**
* 这里写业务逻辑
*/
public void foo(){
}
}

这种方式不但可以避免多线程的问题,还可以避免反序列化重新创建对象造成单例变多例的问题。