当前位置: 首页 > news >正文

设计模式实战——开发中常用到的单例模式

单例模式介绍

单例模式是一种常用的软件设计模式,它确保一个类只有一个实例,并提供一个全局访问点来获取这个实例。以下是对单例模式的介绍:

一、定义与特点

  1. 定义:单例模式(Singleton Pattern)是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点来获取这个实例。
  2. 特点
    • 单一实例:通过私有构造函数或静态方法确保整个应用中只存在一个实例对象。
    • 全局访问点:提供一个静态方法供外部调用,以获取该类的唯一实例。
    • 延迟初始化:在需要时才创建实例,以节省资源并提高效率。

二、实现方式

单例模式的实现方式有多种,主要包括饿汉式、懒汉式、双重校验锁和静态内部类等。

  1. 饿汉式:在类加载时就完成实例化,避免了线程同步问题,但可能导致资源浪费。
  2. 懒汉式:在第一次使用时进行实例化,虽然节约了资源,但在多线程环境下需要加锁以保证线程安全。
  3. 双重校验锁:结合了饿汉式和懒汉式的优点,既实现了延迟加载,又保证了线程安全,但实现较为复杂。
  4. 静态内部类:利用了Java语言的特性,既实现了延迟加载,又保证了线程安全,且实现简单。

三、应用场景

单例模式广泛应用于需要频繁创建和销毁的对象的场景,如数据库连接池、线程池、缓存、日志对象等。这些场景中,使用单例模式可以避免频繁创建和销毁对象带来的性能开销,提高系统效率。

四、优缺点

  1. 优点
    • 节约资源:避免了频繁创建和销毁对象所带来的性能开销。
    • 控制实例数目:确保在整个系统中某个类有且仅有一个实例。
    • 全局访问点:提供了一个统一的访问点来获取该类的实例。
  2. 缺点
    • 灵活性差:由于单例模式限制了类的实例化,因此在需要多个实例的情况下不适用。
    • 扩展困难:当需要继承单例类时,可能会因为单例模式的限制而变得困难。
    • 测试不便:由于单例模式的存在,可能会给单元测试带来一定的困难。

总之,单例模式是一种实用的设计模式,适用于那些需要频繁创建和销毁的对象的场景。在实际开发中,应根据具体需求选择合适的实现方式,并注意避免其潜在的缺点。

JDK中的单例模式 

一、介绍

JDK中的单例模式主要体现在Runtime类和GUI相关类中。以下是对JDK中的单例模式的具体介绍:

  1. Runtime类
    • 定义与特点:在Java中,每个Java应用程序都有一个与之关联的Runtime实例,这个实例封装了Java运行时的环境。由于Java是单进程的,因此在一个JVM进程中,只能有一个Runtime实例,这正符合单例模式的特点。
    • 实现方式:Runtime类使用了饿汉式单例模式,即在类加载时就创建好一个静态的对象供外部使用。这种方式简单且线程安全,因为实例在类加载时就已经创建完成,后续访问不涉及同步问题。
  2. GUI相关类
    • 定义与特点:除了Runtime类外,JDK中的GUI相关类也采用了单例模式。这些类通常在第一次使用时才进行实例化,以避免影响JVM的启动速度。
    • 实现方式:与Runtime类不同,GUI相关类采用的是懒汉式单例模式,即在真正需要的时候再创建实例。这种方式虽然节省了资源,但在多线程环境下需要额外的同步措施来保证线程安全。
  3. 其他应用
    • 枚举实现:在JDK中,还可以通过枚举类型来实现单例模式。枚举类型本身具有单例的特性,且线程安全,无需额外的同步措施。
    • 双重校验锁:双重校验锁(DCL)是一种常用的懒汉式单例模式变种,它结合了饿汉式和懒汉式的优点,既实现了延迟加载,又保证了线程安全。

综上所述,JDK中的单例模式主要应用于Runtime类和GUI相关类中,它们分别采用了饿汉式和懒汉式的实现方式。这些单例模式的应用不仅提高了系统的性能和资源利用率,还为开发者提供了便捷的全局访问点。

二、实例

Runtime类封装了Java运行时的环境。每一个java程序实际上都是启动了一个JVM进程,那么每个JVM进程都是对应这一个Runtime实例,此实例是由JVM为其实例化的。每个 Java 应用程序都有一个 Runtime 类实例,使应用程序能够与其运行的环境相连接。

由于Java是单进程的,所以,在一个JVM中,Runtime的实例应该只有一个。所以应该使用单例来实现。

public class Runtime {private static Runtime currentRuntime = new Runtime();public static Runtime getRuntime() {return currentRuntime;}private Runtime() {}
}

 以上代码为JDK中 Runtime类的部分实现,可以看到,这其实是饿汉式单例模式。在该类第一次被classloader加载的时候,这个实例就被创建出来了。

Spring中的单例模式 

一、介绍

Spring中的单例模式主要体现在Bean的默认作用域上,即singleton

在Spring框架中,单例模式是Bean默认的作用域,这意味着每个由Spring容器管理的Bean默认都是单例的。这种设计可以有效减少对象的创建和销毁次数,从而提高程序的性能和效率。当IOC容器维护Bean实例时,如果一个对象已经被创建了,那么以后每次请求该对象时,都会直接返回之前创建好的对象实例,避免了重复创建和销毁对象的开销。

Spring中的单例模式主要通过配置文件和注解两种方式来实现。在配置文件中,可以通过设置元素的scope属性为"singleton"来指定Bean的作用域为单例。而在注解方式中,可以使用@Component和@Scope("singleton")注解来定义一个单例Bean。

Spring中的单例模式虽然与经典的单例模式有所不同,但它同样遵循了“系统中只有一个实例”的原则,并且提供了全局访问点来获取这个实例。然而,由于Spring容器的特殊性,同一个类在不同容器中可能会有不同的实例,这与经典的单例模式有所区别。

总的来说,Spring中的单例模式是一种便捷且高效的方式来管理Bean的生命周期和作用域。它不仅可以提高系统的性能和效率,还可以简化Bean的配置和管理过程。

 二、实例

我们知道在 Spring中默认注入的Bean都是单例,那么Spring中的单例是怎么生成的呢?我们来看下Spring生成Bean的代码。

@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {Object singletonObject = this.singletonObjects.get(beanName);if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {synchronized (this.singletonObjects) {singletonObject = this.earlySingletonObjects.get(beanName);if (singletonObject == null && allowEarlyReference) {ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);if (singletonFactory != null) {singletonObject = singletonFactory.getObject();this.earlySingletonObjects.put(beanName, singletonObject);this.singletonFactories.remove(beanName);}}}}return singletonObject;
}

spring依赖注入时,使用了双重判断加锁的单例模式,首先从缓存MAP中获取bean实例,如果为null,对缓存map加锁,然后再从缓存中获取bean,如果继续为null,就创建一个bean。

Spring并没有使用私有构造方法来创建bean,而是通过singletonFactory.getObject()返回具体beanName对应的ObjectFactory来创建bean。实际上是调用了AbstractAutowireCapableBeanFactory的doCreateBean方法,返回了BeanWrapper包装并创建的bean实例。

MyBatis中的单例模式 

一、介绍

MyBatis中的单例模式主要体现在其VFS(Virtual File System)组件中

VFS是MyBatis框架中的一个关键组件,用于查找和管理资源文件,如映射器XML文件。在MyBatis的实现中,VFS采用了单例模式来确保整个应用中只有一个VFS实例。这种设计可以有效地减少资源的消耗,并提高文件查找的效率。

MyBatis通过创建一个静态内部类来持有VFS的单例实例。这个静态内部类在被首次访问时才会加载,从而实现了懒加载的效果。具体来说,VFS类中有一个名为VFSHolder的静态内部类,它包含了一个静态的VFS实例。当调用VFS的getInstance()方法时,会返回VFSHolder中持有的这个唯一实例。

这种实现方式不仅保证了线程安全,还避免了饿汉式单例可能导致的资源浪费问题。因为只有在真正需要使用VFS时,才会创建其实例,从而节省了系统资源。

总的来说,MyBatis中的单例模式是一种高效且实用的设计模式,它通过确保VFS组件的唯一性,提高了资源利用率和系统性能。

二、实例

1. ErrorContext

ErrorContext是用在每个线程范围内的单例,用于记录该线程的执行环境错误信息。

public class ErrorContext {private static final String LINE_SEPARATOR = System.getProperty("line.separator","\n");private static final ThreadLocal<ErrorContext> LOCAL = new ThreadLocal<ErrorContext>();private ErrorContext stored;private String resource;private String activity;private String object;private String message;private String sql;private Throwable cause;private ErrorContext() {}public static ErrorContext instance() {ErrorContext context = LOCAL.get();if (context == null) {context = new ErrorContext();LOCAL.set(context);}return context;}}

构造函数是private修饰,具有一个static的局部instance变量和一个获取instance变量的方法,在获取实例的方法中,先判断是否为空如果是的话就先创建,然后返回构造好的对象。

只是这里有个有趣的地方是,LOCAL的静态实例变量使用了ThreadLocal修饰,也就是说它属于每个线程各自的数据,而在instance()方法中,先获取本线程的该实例,如果没有就创建该线程独有的ErrorContext。

也就是说 ErrorContext是线程范围内的单例,而不是全局范围内(JVM内)的单例。

2. VFS

public abstract class VFS {private static final Log log = LogFactory.getLog(VFS.class);/** The built-in implementations. */public static final Class<?>[] IMPLEMENTATIONS = { JBoss6VFS.class, DefaultVFS.class };/** The list to which implementations are added by {@link #addImplClass(Class)}. */public static final List<Class<? extends VFS>> USER_IMPLEMENTATIONS = new ArrayList<Class<? extends VFS>>();/** Singleton instance. */private static VFS instance;/*** Get the singleton {@link VFS} instance. If no {@link VFS} implementation can be found for the* current environment, then this method returns null.*/@SuppressWarnings("unchecked")public static VFS getInstance() {if (instance != null) {return instance;}
}

VFS是MyBatis中提供的文件系统类,存在感比较低。但是我们看下这个类的源代码的话,的确是很标准的单例模式。

Log4j中的单例

Log4j中的单例是指Log4j框架中用于记录日志的对象,通常是一个Logger对象。在Log4j中,每个Logger对象都是单例的,这意味着在整个应用程序中,对于同一个类名或包名,只会创建一个Logger实例。这样可以确保日志记录的一致性和性能优化。

要获取一个Logger对象,可以使用以下代码:

import org.apache.log4j.Logger;public class MyClass {private static final Logger logger = Logger.getLogger(MyClass.class);public void myMethod() {logger.info("This is an info message");logger.error("This is an error message");}
}

在这个例子中,logger是一个静态的Logger对象,它通过调用Logger.getLogger()方法并传入当前类的Class对象来获取。这样,无论在哪个地方使用这个Logger对象,都会得到相同的实例,从而实现单例模式。

注意

Log4j框架内部使用了多种机制来确保多个Logger向同一个文件中打日志时的高效性和线程安全性。

  1. 高效的日志记录:Log4j使用异步日志记录机制,这意味着日志消息不会立即写入文件,而是先存储在内存中的一个缓冲区中。当缓冲区达到一定大小或者在一定时间间隔后,才会将缓冲区中的日志消息批量写入文件。这种方式可以减少磁盘I/O操作的次数,从而提高日志记录的效率。

  2. 线程安全:虽然Log4j的Logger对象是单例的,但实际的日志记录过程是由内部的Appender负责的。Appender负责将日志消息写入目标位置(如文件、控制台等)。Log4j提供了多种Appender实现,其中一些支持多线程访问,例如FileAppenderRollingFileAppender。这些Appender内部使用了同步机制来确保线程安全,例如使用锁或其他并发工具来避免多个线程同时写入同一个文件。

  3. 配置优化:为了进一步提高性能和线程安全性,可以通过合理配置Log4j来实现。例如,可以设置合适的缓冲区大小和刷新间隔,以及选择合适的Appender类型。此外,还可以通过调整日志级别来减少不必要的日志记录,从而减轻系统负担。

总之,Log4j通过异步日志记录、线程安全的Appender实现以及合理的配置优化,能够高效地处理多个Logger向同一个文件中打日志的情况。

相关文章:

  • 二叉树进阶
  • MySQL 中删除重复的数据并只保留一条
  • Pandas和matplotlib实现同期天气温度对比
  • 【计算机网络 - 基础问题】每日 3 题(二十三)
  • ArcGIS Desktop使用入门(三)常用工具条——拓扑(下篇:地理数据库拓扑)
  • 【机器学习】13-决策树2——决策树生成、剪枝
  • Ubuntu上如何优雅下载huggingface上某个gguf模型文件
  • 解决 ValueError: did not find HDF5 headers----安装netCDF4报错
  • Elasticsearch分布式搜索引擎入门
  • Anaconda虚拟环境创建和配置以使用PyTorch和DGL
  • 机器学习课程学习周报十三
  • LLM - 使用 XTuner 指令微调 多模态大语言模型(InternVL2) 教程
  • OJ在线评测系统 后端 判题机模块预开发 架构分析 使用工厂模式搭建
  • 【在Linux世界中追寻伟大的One Piece】进程间通信
  • Rapid品牌SSL证书通配符单域名申请窍门
  • ES学习笔记(12)--Symbol
  • Facebook AccountKit 接入的坑点
  • go语言学习初探(一)
  • java中具有继承关系的类及其对象初始化顺序
  • node学习系列之简单文件上传
  • Swoft 源码剖析 - 代码自动更新机制
  • vue-router的history模式发布配置
  • 翻译 | 老司机带你秒懂内存管理 - 第一部(共三部)
  • 官方解决所有 npm 全局安装权限问题
  • 如何在 Tornado 中实现 Middleware
  • 实现简单的正则表达式引擎
  • 我这样减少了26.5M Java内存!
  • 延迟脚本的方式
  • ​如何使用ArcGIS Pro制作渐变河流效果
  • ​软考-高级-信息系统项目管理师教程 第四版【第19章-配置与变更管理-思维导图】​
  • ‌前端列表展示1000条大量数据时,后端通常需要进行一定的处理。‌
  • #【QT 5 调试软件后,发布相关:软件生成exe文件 + 文件打包】
  • #QT(智能家居界面-界面切换)
  • #宝哥教你#查看jquery绑定的事件函数
  • (2021|NIPS,扩散,无条件分数估计,条件分数估计)无分类器引导扩散
  • (4) PIVOT 和 UPIVOT 的使用
  • (Python第六天)文件处理
  • (Redis使用系列) Springboot 使用redis的List数据结构实现简单的排队功能场景 九
  • (阿里云万网)-域名注册购买实名流程
  • (差分)胡桃爱原石
  • (二)基于wpr_simulation 的Ros机器人运动控制,gazebo仿真
  • (二十六)Java 数据结构
  • (附源码)node.js知识分享网站 毕业设计 202038
  • (附源码)spring boot校园拼车微信小程序 毕业设计 091617
  • (附源码)springboot助农电商系统 毕业设计 081919
  • (附源码)ssm基于jsp高校选课系统 毕业设计 291627
  • (转)拼包函数及网络封包的异常处理(含代码)
  • (转载)跟我一起学习VIM - The Life Changing Editor
  • (轉貼) UML中文FAQ (OO) (UML)
  • ..回顾17,展望18
  • .NET Core、DNX、DNU、DNVM、MVC6学习资料
  • .NET Framework 4.6.2改进了WPF和安全性
  • .net 重复调用webservice_Java RMI 远程调用详解,优劣势说明
  • .NET4.0并行计算技术基础(1)
  • .NET国产化改造探索(三)、银河麒麟安装.NET 8环境