设计模式一

单例模式(Singleton Pattern)是一种常用的软件设计模式,旨在确保一个类只有一个实例,并提供一个全局访问点。单例模式常用于控制资源密集型对象的创建,如数据库连接池、线程池等,以避免资源浪费。

单例模式有多种实现方式,以下是几种常见的实现方法:

### 1. 懒汉式(线程不安全)
```java
public class Singleton {
    private static Singleton instance;

    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
```
这种实现方式在第一次调用`getInstance()`方法时创建实例,但线程不安全,可能导致多个实例被创建。

### 2. 懒汉式(线程安全)
```java
public class Singleton {
    private static Singleton instance;

    private Singleton() {}

    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
```
通过将`getInstance()`方法声明为`synchronized`,确保在多线程环境中只有一个线程可以创建实例。

### 3. 饿汉式
```java
public class Singleton {
    private static final Singleton instance = new Singleton();

    private Singleton() {}

    public static Singleton getInstance() {
        return instance;
    }
}
```
在类加载时就创建实例,避免了线程安全问题,但可能导致资源提前占用。

### 4. 静态内部类
```java
public class Singleton {
    private Singleton() {}

    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}
```
利用Java的类加载机制,只有在第一次调用`getInstance()`方法时才会加载`SingletonHolder`类,从而创建实例。

### 5. 枚举实现
```java
public enum Singleton {
    INSTANCE;

    public void someMethod() {
        // ...
    }
}
```
枚举本身就是单例,Java保证枚举只被实例化一次。

### 使用场景
- 控制资源密集型对象的创建,如数据库连接池。
- 需要全局访问点的配置信息或资源。

### 注意事项
- 单例对象必须自行管理资源,避免内存泄漏。
- 单例模式可能使得系统难以测试,因为它依赖于全局状态。
- 在多模块系统中,单例可能引起不必要的耦合。

选择哪种实现方式取决于具体的需求和上下文。在实际开发中,应根据项目的特点和并发要求来决定使用哪种单例模式的实现。

单例模式除了上述几种实现方式外,还有一些其他考虑因素和变体,以及在使用时可能遇到的一些陷阱:

### 6. 双重检查锁定(Double-Checked Locking, DCL)
```java
public class Singleton {
    private static volatile Singleton instance;

    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}
```
双重检查锁定是一种在多线程环境下实现线程安全的方式,它首先检查实例是否已经创建,如果未创建则进入同步代码块中再次检查,如果仍然未创建则创建实例。`volatile`关键字确保`instance`变量的读写操作对所有线程可见。

### 7. 静态初始化块
```java
public class Singleton {
    private static Singleton instance;

    static {
        instance = new Singleton();
    }

    private Singleton() {}

    public static Singleton getInstance() {
        return instance;
    }
}
```
通过静态初始化块在类加载时创建实例,实现饿汉式单例。

### 8. 使用容器或框架
在某些情况下,可以使用依赖注入容器(如Spring框架)来管理单例对象的生命周期,这样可以避免直接在代码中实现单例模式。

### 9. 反射攻击
单例模式的一个潜在问题是反射攻击,通过反射可以绕过私有构造函数创建新的实例。为了防御反射攻击,可以在构造函数中添加检查逻辑:
```java
private Singleton() {
    if (instance != null) {
        throw new RuntimeException("Use getInstance() method to get the single instance of this class.");
    }
}
```

### 注意事项和陷阱
- **序列化问题**:如果单例对象实现了`Serializable`接口,那么在反序列化时可能会创建多个实例。可以通过实现`readResolve`方法来避免这个问题。
- **反序列化防御**:
```java
protected Object readResolve() {
    return getInstance();
}
```
- **多ClassLoader问题**:不同的`ClassLoader`加载同一个类可能会创建不同的实例。这通常在OSGi等模块化系统中发生。
- **枚举实现的局限性**:枚举实现是线程安全的,且天然防御反射攻击和序列化问题,但可能不适用于需要延迟初始化的场景。

### 总结
单例模式是一种创建型设计模式,它确保了一个类只有一个实例,并提供了一个全局访问点。在实现单例模式时,需要考虑线程安全、延迟初始化、序列化、反射攻击等问题。选择哪种实现方式取决于项目的具体需求和上下文。在某些情况下,使用依赖注入容器来管理单例对象可能是更好的选择。

在讨论单例模式时,除了实现方式和注意事项,还可以探讨一些更深入的话题,包括单例模式的优缺点、适用场景以及与其他设计模式的结合使用。

 

### 单例模式的优缺点

 

#### 优点:

1. **控制实例数量**:在内存中只有一个实例,节省资源。

2. **全局访问点**:提供了一个全局的访问点,简化了访问逻辑。

3. **延迟初始化**:可以在需要的时候才创建实例,提高系统启动速度。

4. **线程安全**:通过各种机制确保了多线程环境下的线程安全。

 

#### 缺点:

1. **代码耦合**:单例模式增加了代码的耦合性,使得单元测试变得困难。

2. **扩展性差**:单例模式的扩展性较差,一旦需要修改单例的行为,可能需要重构整个系统。

3. **资源限制**:单例模式限制了实例的数量,可能不适用于需要多个实例的场景。

4. **生命周期管理**:单例对象的生命周期与应用程序相同,可能导致内存泄漏。

 

### 适用场景:

- 当一个全局对象需要被频繁访问,且只允许一个实例存在时,如配置管理器、线程池等。

- 当需要控制资源访问,如数据库连接池,以避免资源被滥用时。

 

### 与其他设计模式的结合使用:

- **工厂方法模式**:可以结合工厂方法模式来创建单例对象,实现更灵活的实例创建逻辑。

- **观察者模式**:单例对象可以作为事件发布者,结合观察者模式实现事件驱动的系统。

- **原型模式**:在某些情况下,可以使用原型模式来创建单例对象的副本,但这些副本共享相同的状态。

 

### 设计模式的选择与权衡:

在实际的软件开发中,选择使用单例模式需要权衡其优缺点。有时,其他设计模式如依赖注入(DI)可能更适合解决资源管理问题。依赖注入通过容器来管理对象的生命周期和依赖关系,而不是在代码中硬编码,这使得系统更加灵活和易于测试。

 

### 总结:

单例模式是一种简单而强大的设计模式,但它并不是万能的。在决定使用单例模式之前,应该仔细考虑其适用性,并考虑其他可能的设计模式或架构方法。设计模式的选择应该基于对问题域的深入理解和对系统要求的全面考虑。

 

在讨论单例模式时,除了其实现、优缺点和适用场景,还可以进一步探讨单例模式在实际应用中的设计原则和最佳实践。

### 设计原则
1. **单一职责原则 (Single Responsibility Principle, SRP)**:确保单例类只负责一个功能,避免将过多的职责放在单例类中。

2. **开放-封闭原则 (Open-Closed Principle, OCP)**:设计时应该对扩展开放,对修改封闭。单例模式本身是封闭的,因为它限制了实例的数量,但可以通过设计良好的接口来实现对扩展的开放。

3. **里氏替换原则 (Liskov Substitution Principle, LSP)**:确保单例类可以被其基类或接口替换,而不影响系统的其他部分。

4. **接口隔离原则 (Interface Segregation Principle, ISP)**:如果单例类实现了多个接口,那么客户端应该只依赖于它需要的接口。

5. **依赖倒置原则 (Dependency Inversion Principle, DIP)**:高层模块不应依赖于低层模块,两者都应该依赖于抽象。单例类应依赖于抽象接口,而不是具体实现。

### 最佳实践
1. **最小化单例类的功能**:单例类应该只包含必要的功能,避免变成“上帝对象”。

2. **使用接口**:为单例类定义一个接口,这样可以在不修改客户端代码的情况下替换单例的实现。

3. **避免全局状态**:单例模式可能导致全局状态,应该尽量避免。如果需要存储全局状态,考虑使用其他方式,如配置管理器。

4. **考虑使用依赖注入**:依赖注入可以提供更灵活的对象创建和管理方式,减少对单例模式的依赖。

5. **实现适当的序列化和反序列化逻辑**:如果单例类需要被序列化,确保其`readResolve`方法能够返回现有的单例实例,避免创建新的实例。

6. **文档化**:由于单例模式的特殊性,应该在文档中清楚地说明其使用方式和限制。

7. **使用枚举实现单例**:如果可能,使用枚举实现单例是最简单和最安全的方式,因为它天然是线程安全的,并且可以防止反序列化创建新实例。

8. **避免滥用单例**:单例模式应该只在确实需要全局唯一实例时使用。滥用单例模式会导致代码难以理解和维护。

9. **考虑使用服务定位器模式**:在某些情况下,可以使用服务定位器模式来代替单例模式,它提供了一种更灵活的方式来查找和访问服务。

10. **测试单例类**:确保单例类是可测试的。使用依赖注入和模拟对象可以使单例类的测试变得更加容易。

通过遵循这些设计原则和最佳实践,可以更有效地使用单例模式,同时减少其潜在的问题。记住,设计模式是解决问题的工具,而不是束缚。在选择和使用单例模式时,应该根据具体情况做出最合适的决策。

在继续深入单例模式的讨论时,我们可以考虑以下几个方面:

### 单例模式的测试
单例模式的测试可能比较复杂,因为它涉及到全局状态和线程安全问题。以下是一些测试单例模式时的注意事项:

1. **测试单例保证**:确保在程序的整个生命周期内,只创建了一个实例。
2. **多线程测试**:在多线程环境下测试单例模式,确保没有线程安全问题。
3. **序列化测试**:如果单例类实现了`Serializable`接口,测试其序列化和反序列化过程,确保不会创建额外的实例。
4. **反序列化防御**:验证`readResolve`方法是否正确实现,防止通过反序列化创建新的实例。

### 单例模式的替代方案
在某些情况下,单例模式可能不是最佳选择。以下是一些替代方案:

1. **依赖注入 (DI)**:通过依赖注入容器管理对象的生命周期,可以避免硬编码单例实现。
2. **全局配置**:对于配置信息,可以使用全局配置类,它不是单例,但可以被所有需要的地方访问。
3. **服务定位器**:服务定位器模式提供了一种查找服务对象的方式,可以作为单例模式的替代。
4. **原型模式**:如果需要创建相似但独立的对象,可以使用原型模式,而不是单例。

### 单例模式的实现技巧
1. **使用静态内部类**:在Java中,静态内部类可以很好地实现线程安全的单例模式,因为它利用了类的加载机制。
2. **延迟初始化**:只在第一次请求单例实例时才创建它,这可以提高应用程序的启动速度。
3. **避免过早优化**:不要过早地优化单例模式的实现,除非确实遇到了性能问题。

### 单例模式的反模式
1. **滥用单例**:在不适当的情况下使用单例模式,比如将单例用于不应该共享的状态。
2. **隐藏的依赖**:单例类可能引入隐藏的依赖,使得代码难以理解和维护。
3. **全局状态**:单例模式可能导致全局状态的使用,这通常是一个坏的实践。

### 总结
单例模式是一个强大的设计模式,但也需要谨慎使用。在实际应用中,应该根据具体需求和上下文来决定是否使用单例模式。同时,要遵循设计原则和最佳实践,确保单例模式的使用是合理和有效的。

此外,测试单例模式时需要特别注意线程安全和序列化问题。在某些情况下,考虑使用依赖注入或其他替代方案可能会更合适。最后,避免滥用单例模式,确保它只在确实需要全局唯一实例时使用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/592742.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

springmvc下

第二类初始化操作 multipartResolver应用 localeResolver应用 themeResolver应用 handlerMapping应用 handlerAdapter应用 handlerExceptionReslver requestToViewNameTranslator应用 viewResolver应用 flashMapManager应用 dispatcherServlet逻辑处理 processRequest处理web请…

【Flask 系统教程 5】视图进阶

类视图 在 Flask 中,除了使用函数视图外,你还可以使用类视图来处理请求。类视图提供了一种更为结构化和面向对象的方式来编写视图函数,使得代码组织更清晰,并且提供了更多的灵活性和可扩展性。 创建类视图 要创建一个类视图&am…

Docker高频使用命令

一、Docker常用命令总结 1.镜像命令管理 指令描述ls列出镜像build构建镜像来自Dockerfilehoistory查看历史镜像inspect显示一个或多个镜像的详细信息pull从镜像仓库拉取镜像push推送一个镜像仓库rm移除一个或多个镜像prune一处未使用的镜像,没有被标记或被任何容器…

初始化Linux或者Mac下Docker运行环境

文章目录 1 Mac下安装Docker2 Linux下安装Docker2.1 确定Linux版本2.2 安装Docker2.3 配置加速镜像 3 Docker安装校验4 安装docker-compose4.1 直接下载二进制文件4.2 移动二进制文件到系统路径4.3 设置可执行权限4.4 验证安装 1 Mac下安装Docker mac 安装 docker 还是比较方便…

哥白尼高程Copernicus DEM下载(CSDN_20240505)

哥白尼数字高程模型(Copernicus DEM, COP-DEM)由欧洲航天局(European Space Agency, 简称ESA或欧空局)发布,全球范围免费提供30米和90米分辨率DEM。COP-DEM是数字表面模型(DSM),它表示地球表面(包括建筑物、基础设施和植被)的高程。COP-DEM是经过编辑的D…

c++set和map

目录 一、set的使用 1、set对象的创建 2、multiset 二、map的使用 1、map对象的创建 2、map的operator[] 序列式容器:vector、list、deque....单纯的存储数据,数据和数据之间没有关联 关联式容器:map、set.....不仅仅是存储数据&#x…

2000-2020年县域创业活跃度数据

2000-2020年县域创业活跃度数据 1、时间:2000-2020年 2、指标:地区名称、年份、行政区划代码、经度、纬度、所属城市、所属省份、年末总人口万人、户籍人口数万人、当年企业注册数目、县域创业活跃度1、县域创业活跃度2、县域创业活跃3 3、来源&#…

python数据可视化:显示两个变量间的关系散点图scatterplot()

【小白从小学Python、C、Java】 【计算机等考500强证书考研】 【Python-数据分析】 python数据可视化: 显示两个变量间的关系 散点图 scatterplot() [太阳]选择题 请问关于以下代码表述错误的选项是? import seaborn as sns import matplotlib.pyplot …

VISO流程图之子流程的使用

子流程的作用 整个流程图的框图多而且大,进行分块;让流程图简洁对于重复使用的流程,可以归结为一个子流程图,方便使用,避免大量的重复性工作; 新建子流程 方法1: 随便布局 框选3 和4 &#…

SQL:NOT IN与NOT EXISTS不等价

在对SQL语句进行性能优化时,经常用到一个技巧是将IN改写成EXISTS,这是等价改写,并没有什么问题。问题在于,将NOT IN改写成NOT EXISTS时,结果未必一样。 目录 一、举例验证二、三值逻辑简述三、附录:用到的S…

3.3Java全栈开发前端+后端(全栈工程师进阶之路)-前端框架VUE3框架-企业级应用-Vue组合式API

为什么要使用Composition API 一个Options API实例 在前面的课程中&#xff0c;我们都是采用 Options API&#xff08;基于选项的 API &#xff09; 来写一个组件的。下面是一个实例&#xff1a; <template> Count is: {{ count }}, doubleCount is: {{ doubleCount…

深入理解网络原理3----TCP核心特性介绍(上)【面试高频考点】

文章目录 前言TCP协议段格式一、确认应答【保证可靠性传输的机制】二、超时重传【保证可靠性传输的机制】三、连接管理机制【保证可靠性传输的机制】3.1建立连接&#xff08;TCP三次握手&#xff09;---经典面试题3.2断开连接&#xff08;四次挥手&#xff09;3.3TCP状态转换 四…

【skill】onedrive的烦人问题

Onedrive的迷惑行为 安装Onedrive&#xff0c;如果勾选了同步&#xff0c;会默认把当前用户的数个文件夹&#xff08;桌面、文档、图片、下载 等等&#xff09;移动到安装时提示的那个文件夹 查看其中的一个文件的路径&#xff1a; 这样一整&#xff0c;原来的文件收到严重影…

政安晨:【Keras机器学习示例演绎】(三十五)—— 使用 LayerScale 的类注意图像变换器

目录 简介 导入 层刻度层 随机深度层 类注意力 会说话的头注意力 前馈网络 其他模块 拼凑碎片&#xff1a;CaiT 模型 定义模型配置 模型实例化 加载预训练模型 推理工具 加载图像 获取预测 关注层可视化 结论 政安晨的个人主页&#xff1a;政安晨 欢迎 &#…

Topaz Video AI 5.0.3激活版 AI视频无损缩放增强

Topaz Video AI专注于很好地完成一些视频增强任务&#xff1a;去隔行&#xff0c;放大和运动插值。我们花了五年时间制作足够强大的人工智能模型&#xff0c;以便在真实世界的镜头上获得自然的结果。 Topaz Video AI 还将充分利用您的现代工作站&#xff0c;因为我们直接与硬件…

【数学建模】矩阵微分方程

一、说明 我相信你们中的许多人都熟悉微分方程&#xff0c;或者至少知道它们。微分方程是数学中最重要的概念之一&#xff0c;也许最著名的微分方程是布莱克-斯科尔斯方程&#xff0c;它控制着任何股票价格。 ​​ 股票价格的布莱克-斯科尔斯模型 微分方程可以由数学中的许多…

MidJourney提示词大全

大家好&#xff0c;我是无界生长。 这篇文章分享一下MidJourney提示词&#xff0c;篇幅内容有限&#xff0c;关注公众号&#xff1a;无界生长&#xff0c;后台回复&#xff1a;“MJ”&#xff0c;获取全部内容。 我是无界生长&#xff0c;如果你觉得我分享的内容对你有帮助&…

ArcGIS软件:地图投影的认识、投影定制

这一篇博客介绍的主要是如何在ArcGIS软件中查看投影数据&#xff0c;如何定制投影。 1.查看地图坐标系、投影数据 首先我们打开COUNTIES.shp数据&#xff08;美国行政区划图&#xff09;&#xff0c;并点击鼠标右键&#xff0c;再点击数据框属性就可以得到以下的界面。 我们从…

【Mac】graphpad prism for Mac(专业医学绘图工具) v10.2.3安装教程

软件介绍 GraphPad Prism for Mac是一款专业的科学数据分析和绘图软件&#xff0c;广泛用于生物医学和科学研究领域。它具有强大的统计分析功能&#xff0c;可以进行各种数据分析&#xff0c;包括描述性统计、生存分析、回归分析、方差分析等。同时&#xff0c;它还提供了丰富…

C++奇迹之旅:string类接口详解(上)

文章目录 &#x1f4dd;为什么学习string类&#xff1f;&#x1f309; C语言中的字符串&#x1f309;string考察 &#x1f320;标准库中的string类&#x1f309;string类的常用接口说明&#x1f320;string类对象的常见构造 &#x1f6a9;总结 &#x1f4dd;为什么学习string类…
最新文章