单例设计模式
单例设计模式
- 有一些对象其实我们只需要一个,比如说:线程池、缓存、对话框、注册表、日志对象、充当打印机、显卡等设备驱动程序的对象。事实上,这一类对象只能有一个实例,如果制造出多个实例就可能会导致一些问题的产生,比如:程序的行为异常、资源使用过量、或者不一致性的结果。
1. 什么是单例设计模式
- 单例设计模式(Singleton Pattern)是一种常用的软件设计模式,它的主要目的是确保一个类只有一个实例,并提供一个全局访问点来获取该实例。
- 单例模式通常用于需要全局控制访问的资源,例如配置管理器、日志记录器、线程池等。
- 通过单例模式,可以避免重复创建对象,节省资源,并确保在整个应用程序中只有一个实例存在,从而实现对共享资源的有效管理。
2. 单例设计模式的好处
-
对于频繁使用的对象,可以省略创建对象所花费的时间,减少系统开销
-
由于new操作的次数减少,因为对于系统内存的使用频率也会降低,可以减轻GC(Garbage Collection,垃圾回收器)的压力,缩短GC的停顿时间
-
Spring中的Bean的默认作用域就是singleton(单例)的。除了singleton作用域外,Spring还支持其他几种作用域:
- prototype(原型):每次请求都会创建一个新的bean实例
- request(仅Web应用可用):每个HTTP请求都会创建一个新的bean实例(请求bean),该bena仅在当前HTTP request内有效
- session(仅Web应用可用):每一次来自新session的HTTP请求都会创建一个新的bean实例(会话bean),该bean仅在当前HTTP session内有效
- application/global-session(仅Web应用可用):每个Web应用在启动时创建一个bean(应用bena),该bean仅在当前应用启动时间内有效
- websocket(仅Web应用可用):每个WebSocket会话都会创建一个新的bean实例(WebSocket bean)
3. 单例设计模式的实现方式
- Spring通过
ConcurrentHashMap实现单例注册表的特殊方式实现单例模式 - 核心代码如下:
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// 通过 ConcurrentHashMap(线程安全) 实现单例注册表
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(64);
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
Assert.notNull(beanName, "'beanName' must not be null");
synchronized (this.singletonObjects) {
// 检查缓存中是否存在实例
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
//...省略了很多代码
try {
singletonObject = singletonFactory.getObject();
}
//...省略了很多代码
// 如果实例对象在不存在,我们注册到单例注册表中。
addSingleton(beanName, singletonObject);
}
return (singletonObject != NULL_OBJECT ? singletonObject : null);
}
}
//将对象添加到单例注册表
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
this.singletonObjects.put(beanName, (singletonObject != null ? singletonObject : NULL_OBJECT));
}
}
4. 单例Bean存在线程安全问题吗?
- 大部分时候我们并没有在项目中使用多线程,所以很少有人会关注这个问题。单例 Bean 存在线程问题,主要是因为当多个线程操作同一个对象的时候是存在资源竞争的。
- 常见的有两种解决办法:
- 在Bean中尽量避免定义可变的成员变量。
- 在类中定义一个
ThreadLocal成员变量,将需要的可变成员变量保存在ThreadLocal中(推荐的一种方式)。不过,大部分Bean实际都是无状态(没有实例变量)的(比如Dao、Service),这种情况下, Bean是线程安全的
