设计模式:单例模式

单例模式

提出问题

  • 有些对象其实我们只需要一个。如果制造出多个,就会导致许多问题的产生。
    • 如线程池、缓存、对话框、处理偏好设置和注册表的对象、日志对象,充当打印机、显卡设备的驱动程序的对象。
    • 例如配置文件对象,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理。
  • 使用全局变量存在缺陷。
    • 如果使用全局变量,则必须在程序一开始就创建好对象。如果对象非常消耗资源,而程序在这次执行中又没有使用到它,则形成浪费。

概述

是什么

单例模式:确保一个类只有一个实例,并提供一个全局访问点

分类

应用

适用性

  • 当类只能有一个实例,而且客户可以从一个众所周知的访问点访问它。
  • 当这个唯一实例应该是通过子类化可扩展的,并且客户应该无需更改代码就能使用一个扩展的实例。
  • 需要频繁实例化然后销毁的对象。
  • 创建对象时耗时过多或者耗资源过多,但又经常用到的对象
  • 有状态的工具类对象。
  • 频繁访问数据库或文件的对象。

案例

资源共享的情况下,避免由于资源操作时导致的性能或损耗等。如上述中的日志文件,应用配置。

控制资源的情况下,方便资源之间的互相通信。如线程池等。

协作

结构

1564056712943

参与者

协作

  • 类关系
  • 逻辑关系

效果

  • 对唯一实例的受控访问。因为Singleton类封装它的唯一实例,所以可以严格控制客户怎样以及何时访问它。
  • 缩小名空间。Singleton是对全局变量的一种改进,避免了那些存储唯一实例的全局变量污染名空间。
  • 允许对操作和表示的精化。Singleton可以有子类,而且用这个扩展类的。
  • 允许可变数目的实例。
  • 比类操作更为灵活。

权衡

基础

优缺

  • 为什么全局变量比单例差:
    • 急切实例化VS延迟实例化。
    • 全局变量可以提供全局访问,但是不能保证只有一个实例。而且全局变量指向很多小对象会造成命名空间的污染。
  • 为什么不创建一个类,把所有的方法和变量定义为静态,将类当做一个单例:
    • 如果类自给自足,而且不依赖于复杂的初始化,那么OK的。
    • 但静态初始化的控制权是在Java手上,并且当很多类牵扯其中时,就可能有一些与初始化次序有关的bug。
  • 由于在系统内存中只存在一个对象,因此可以 节约系统资源,当 需要频繁创建和销毁的对象时单例模式无疑可以提高系统的性能。
  • 避免对共享资源的多重占用。

实现

懒汉式

线程不安全法
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
/**
* 懒汉模式
* 单例实例在第一次使用时候进行创建
*/
@NotRecommend
public class SingletonExample1 {

//私有的构造函数
//即其他途径无法创建这个类的对象
private SingletonExample1(){
//包含对资源的处理等等
}

//单例对象
private static SingletonExample1 instance = null;

//静态的工厂方法
//public
public static SingletonExample1 getInstance(){
//多线程环境很容易出现问题
if (instance == null){
instance = new SingletonExample1();
}
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
31
32
33
34
35
36
37
/**
* 懒汉模式 --> 双重同步锁单例模式
* 单例实例在第一次使用时候进行创建
*/
@NotRecommend
public class SingletonExample4 {

//私有的构造函数
private SingletonExample4(){}

// 1.memory = allocate() 分配对象的内存空间
// 2.ctorInstance() 初始化对象
// 3. instance = memory 设置instance 指向刚分配的内存

//JVM和CPU优化,发生了指令重排

// 1.memory = allocate() 分配对象的内存空间
// 3. instance = memory 设置instance 指向刚分配的内存
// 2.ctorInstance() 初始化对象

//在第三步的时候,instance!=null
//而此时在指令重排下,另一个线程就会获得一个没有初始化对象的引用,并将其返回

//单例对象
private static SingletonExample4 instance = null;

//静态的工厂方法
private static SingletonExample4 getInstance(){
if (instance == null){ //双重检测机制 //B
synchronized (SingletonExample4.class){ //同步锁
if (instance == null){
instance = new SingletonExample4(); //A - 3
}
}
}
return instance;
}
线程安全法

不推荐法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class SingletonExample3 {

//私有的构造函数
private SingletonExample3(){}

//单例对象
private static SingletonExample3 instance = null;

//静态的工厂方法
//synchronized限制,而存在性能开销
private static synchronized SingletonExample3 getInstance(){
if (instance == null){
instance = new SingletonExample3();
}
return instance;
}
}

双重同步锁

基于volatile

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
/**
* 懒汉模式 --> 双重同步锁单例模式
* 单例实例在第一次使用时候进行创建
*/
@ThreadSafe
public class SingletonExample5 {

//私有的构造函数
private SingletonExample5(){}

// 1.memory = allocate() 分配对象的内存空间
// 2.ctorInstance() 初始化对象
// 3. instance = memory 设置instance 指向刚分配的内存

//单例对象 volatitle+ 双重检测机制 -> 禁止指令重排序
private volatile static SingletonExample5 instance = null;

//静态的工厂方法
private static SingletonExample5 getInstance(){
if (instance == null){ //双重检测机制 //B
synchronized (SingletonExample5.class){ //同步锁
if (instance == null){
instance = new SingletonExample5(); //A - 3
}
}
}
return instance;
}
}

饿汉模式

通过静态域实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 饿汉模式
* 单例实例在装载使用时候进行创建
*/
@ThreadSafe
public class SingletonExample2 {

//私有的构造函数
private SingletonExample2(){
//如果构造方法中存在过多的功能,则在加载时会过慢,存在性能问题
//只进行资源加载而没有实际调用,则会导致资源浪费
}

//单例对象
private static SingletonExample2 instance = new SingletonExample2();

//静态的工厂方法
private static SingletonExample2 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
@ThreadSafe
public class SingletonExample6 {

//私有的构造函数
private SingletonExample6(){}
//静态资源是顺序执行的
//单例对象
private static SingletonExample6 instance = null;
//必须写在后面,如果写在前面,则会被上一句赋值为null
static {
instance = new SingletonExample6();
}

//静态的工厂方法
private static SingletonExample6 getInstance(){
return instance;
}

public static void main(String[] args) {
System.out.println(getInstance().hashCode());
System.out.println(getInstance().hashCode());

}
}

枚举模式

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
/**
* 枚举模式:最安全
*/
@ThreadSafe
@Recommend
public class SingletonExample7 {
//私有的构造函数
private SingletonExample7(){}

public static SingletonExample7 getInstance(){
//在实际使用的时候才会初始化
return Singleton.INSTANCE.getSingleton();
}

private enum Singleton{
INSTANCE;
private SingletonExample7 singleton;

//JVM保证这个方法绝对只调用一次
Singleton(){
singleton = new SingletonExample7();
}

public SingletonExample7 getSingleton(){
return singleton;
}
}
}

底层原理

与其他的区别

设计思想

进阶

  • 为什么不直接使用synchronized同步getInstance(),简单有效。
    • 因为同步一个方法可能导致程序的执行效率下降100倍,如果这个getInstance()在一个频繁运行的地方,那性能将很差了。
  • 两个类加载器有机会各自创建自己的单例。
    • 自行指定类加载器,指定同一个类加载器。

总结

参考