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

news/2024/9/29 5:06:15/

单例模式介绍

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

一、定义与特点

  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的实例应该只有一个。所以应该使用单例来实现。

java">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的代码。

java">@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是用在每个线程范围内的单例,用于记录该线程的执行环境错误信息。

java">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

java">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对象,可以使用以下代码:

java">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向同一个文件中打日志的情况。


http://www.ppmy.cn/news/1530898.html

相关文章

微积分复习笔记(1):单变量微积分

P.S. 本来不想分篇的&#xff0c;但居然字数超上限了。 2 π x < sin ⁡ x < x ( 0 < x < π 2 ) . \frac{2}{\pi}x<\sin x<x\ (0<x<\frac{\pi}{2}). π2​x<sinx<x (0<x<2π​). x < tan ⁡ x < 4 π x ( 0 < x < π 4 ) . x…

深入探索:MATLAB中的硬件支持包(HSP)及其应用

在MATLAB环境中&#xff0c;硬件支持包&#xff08;HSP&#xff09;扮演着至关重要的角色&#xff0c;尤其是在与硬件交互和嵌入式系统开发方面。HSP提供了一套工具和库&#xff0c;使得MATLAB能够与特定的硬件平台进行有效通信&#xff0c;实现代码的生成和优化。本文将详细介…

select查询表单

select查询语法&#xff1a; select 【1】from 【2】where 【3】 1若为*表示显示全部数据列&#xff0c;若为某一列列名则只显示本列内容&#xff08;也可为多列列名&#xff09;。若在1后面加as ‘c’&#xff0c;则表示把查询的列名换成c。 2为要查询的表表名。 3为查询的…

【CSS】鼠标 、轮廓线 、 滤镜 、 堆叠层级

cursor 鼠标outline 轮廓线filter 滤镜z-index 堆叠层级 cursor 鼠标 值说明值说明crosshair十字准线s-resize向下改变大小pointer \ hand手形e-resize向右改变大小wait表或沙漏w-resize向左改变大小help问号或气球ne-resize向上右改变大小no-drop无法释放nw-resize向上左改变…

Spring Boot 整合MyBatis-Plus 实现多层次树结构的异步加载功能

文章目录 1&#xff0c;前言2&#xff0c;什么是多层次树结构&#xff1f;3&#xff0c;异步加载的意义4&#xff0c;技术选型与实现思路5&#xff0c;具体案例5.1&#xff0c;项目结构5.2&#xff0c;项目配置&#xff08;pom.xml&#xff09;5.3&#xff0c;配置文件&#xf…

华为HarmonyOS灵活高效的消息推送服务(Push Kit) - 4 获取Push Token

场景介绍 注意 Push Kit在您获取Push Token时进行了推送服务权益校验&#xff0c;请您在进行开发前先阅读开通推送服务章节&#xff0c;完成相关配置。 Push Token标识了每台设备上每个应用&#xff0c;开发者调用getToken()接口向Push Kit服务端请求Token&#xff0c;获取到…

无人机之侦测技术篇

无人机的侦测技术是综合利用多种传感器来“发现”或“找到”无人机目标&#xff0c;并通过分析其物理属性&#xff08;如光学特性、热学特性、声学特性、磁学特性&#xff09;来进行识别和跟踪。 一、雷达探测 原理&#xff1a;雷达系统通过发射电磁波&#xff0c;利用无人机…

Unity 设计模式 之 【什么是设计模式】/ 【为什么要使用设计模式】/ 【架构和设计模式的区别】

Unity 设计模式 之 【什么是设计模式】/ 【为什么要使用设计模式】/ 【架构和设计模式的区别】 目录 Unity 设计模式 之 【什么是设计模式】/ 【为什么要使用设计模式】/ 【架构和设计模式的区别】 一、简单介绍 二、 Unity 设计模式 1、Unity 开发中使用设计模式的特点 2…