单例模式

单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。总之,选择单例模式就是为了避免不一致状态。

系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能。

单例模式的特点

  • 单例类只能有一个实例。
  • 单例类必须自己创建自己的唯一实例。
  • 单例类必须给所有其他对象提供这一实例。
  • 单例模式保证了全局对象的唯一性,比如系统启动读取配置文件就需要单例保证配置的一致性。

单例的四大原则

  • 构造私有
  • 以静态方法或者枚举返回实例
  • 确保实例只有一个,尤其是多线程环境
  • 确保反序列化时不会重新构建对象

单例模式的使用场景

  • 需要频繁的进行创建和销毁的对象
  • 创建对象时耗时过多或耗费资源过多,但又经常用到的对象
  • 工具类对象
  • 频繁访问数据库或文件的对象

会破坏单例模式的两种情况

  • 反射
  • 序列化和反序列化

实现单例模式的方式

1、饿汉式

在类加载的时候就完成实例化,本身是线程安全的,如果从始至终从未使用过这个实例,则会造成内存的浪费。

1
2
3
4
5
6
7
8
9
10
11
12
public class Singleton {

private static Singleton instance;

static {
instance = new Singleton();
}

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

2、懒汉式

为了解决饿汉式可能造成的内存浪费问题,希望实例可以在使用的时候再被加载,即实现延迟加载,但是需要考虑到线程安全的问题

  • 实现方式一

加上 synchronized 同步锁,实例化代码执行一次后,后面再次访问时,判断 if (instance== null),直接 return 实例化对象以提高执行效率。

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

private static volatile Singleton instance;

private Singleton() {
}

public static Singleton getInstance() {
// 去掉外层的判断,相当于 public static synchronized Singleton getInstance()
if (instance == null) {
synchronized (Singleton.class) {
// 双重检查,防止有多个线程通过第一层判断后,产生多个实例
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
  • 实现方式二

使用静态内部类,静态内部类只有在调用时才会加载,它保证了 Singleton 实例的延迟初始化,又保证了实例的唯一性。它把 singleton 的实例化操作放到一个静态内部类中,在第一次调用 getInstance() 方法时,JVM 才会去加载 SingletonInstance 类,同时初始化 singleton 实例,所以能让 getInstance() 方法线程安全。

静态内部类虽然保证了单例在多线程并发下的线程安全性,但是在遇到序列化对象时,默认的方式运行得到的结果就是多例的。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Singleton {

private Singleton() {
}

private static class SingletonInstance {
private static final Singleton instance = new Singleton();
}

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

通过枚举实现

通过枚举实现不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象

但是不是延迟加载的,并且很多框架都是使用反射进行构造实例的

1
2
3
4
5
6
7
8
9
10
11
public enum SingletonEnum {
// 枚举元素本身就是单例
INSTANCE;

/**
* 其他要执行的方法
*/
public void sayHello() {
System.out.println("你好");
}
}

测试序列化和反序列化

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
public class SingletonStaticInnerSerializeTest {

public static void main(String[] args) {
try {
// 查看初始的hashCode
Singleton instance = Singleton.getInstance();
System.out.println(instance.hashCode());

//序列化
FileOutputStream fo = new FileOutputStream("test");
ObjectOutputStream oo = new ObjectOutputStream(fo);
oo.writeObject(instance);
oo.close();
fo.close();

//反序列化
FileInputStream fi = new FileInputStream("test");
ObjectInputStream oi = new ObjectInputStream(fi);
Singleton serialize = (Singleton) oi.readObject();
oi.close();
fi.close();
// 查看反序列化后的hashCode
System.out.println(serialize.hashCode());
} catch (Exception e) {
e.printStackTrace();
}
}
}

class Singleton implements Serializable {

private static final long serialVersionUID = 1L;

private Singleton() {
}

private static class SingletonInstance {
private static final Singleton instance = new Singleton();
}

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

结果

1
2
1735600054
764977973

解决方法

在反序列化中使用 readResolve()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Singleton implements Serializable {

private static final long serialVersionUID = 1L;

private Singleton() {
}

private static class SingletonInstance {
private static final Singleton instance = new Singleton();
}

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

private Object readResolve() {
System.out.println("调用了readResolve方法");
return SingletonInstance.instance;
}
}

结果

1
2
3
1735600054
调用了readResolve方法
1735600054