Java设计模式之单例模式详细讲解和案例示范

单例模式(Singleton Pattern)是Java设计模式中最简单但却非常实用的一种。它确保一个类只有一个实例,并提供一个全局的访问点。本文将通过电商交易系统为例,详细探讨单例模式的使用场景、常见问题及解决方案。

1. 单例模式简介

1.1 什么是单例模式?

单例模式是一种创建型设计模式,它确保某个类在系统中只有一个实例存在,并提供一个全局访问该实例的方式。这种模式适用于以下情况:

  • 需要控制资源的唯一性:如数据库连接池、配置文件管理器等。
  • 需要对共享资源进行控制:如线程池管理、日志记录器等。

1.2 单例模式的实现方式

实现单例模式的方法有很多种,主要包括:

  1. 懒汉式(Lazy Initialization):只有在第一次使用时才会创建实例。
  2. 饿汉式(Eager Initialization):在类加载时就创建实例。
  3. 双重检查锁(Double-Check Locking):在多线程环境下,通过双重检查确保单例对象的唯一性。
  4. 静态内部类:利用Java类加载机制来保证线程安全。

下面将逐一详细介绍这些实现方式,并给出相应代码示例。

2. 单例模式的实现方式详解

2.1 懒汉式单例模式

懒汉式单例模式是在第一次需要使用实例时才创建对象,适用于在应用启动时不需要立即加载的场景。

2.1.1 代码示例
public class LazySingleton {private static LazySingleton instance;private LazySingleton() {// 私有化构造函数}public static synchronized LazySingleton getInstance() {if (instance == null) {instance = new LazySingleton();}return instance;}
}
2.1.2 适用场景

懒汉式单例适用于实例化开销较大的对象,并且该对象在程序运行初期不一定会被使用的情况。如电商系统中的大数据分析引擎,可能在系统启动时并不需要立即启动。

2.1.3 优缺点

优点

  • 延迟加载,减少内存开销。

缺点

  • 多线程环境下性能较差,因为每次获取实例时都需要进行同步。

2.2 饿汉式单例模式

饿汉式单例模式是在类加载时就创建实例,适用于程序运行过程中必然会使用到的实例。

2.2.1 代码示例
public class EagerSingleton {private static final EagerSingleton instance = new EagerSingleton();private EagerSingleton() {// 私有化构造函数}public static EagerSingleton getInstance() {return instance;}
}
2.2.2 适用场景

饿汉式单例适用于那些启动时需要立即加载并长期使用的对象,如电商系统中的配置管理器(Configuration Manager)。

2.2.3 优缺点

优点

  • 实现简单,线程安全。

缺点

  • 由于实例是在类加载时创建的,可能会导致内存浪费,尤其是在实例一直没有被使用的情况下。

2.3 双重检查锁

双重检查锁机制在多线程环境下使用,确保实例的唯一性和线程安全性。

2.3.1 代码示例
public class DoubleCheckedLockingSingleton {private static volatile DoubleCheckedLockingSingleton instance;private DoubleCheckedLockingSingleton() {// 私有化构造函数}public static DoubleCheckedLockingSingleton getInstance() {if (instance == null) {synchronized (DoubleCheckedLockingSingleton.class) {if (instance == null) {instance = new DoubleCheckedLockingSingleton();}}}return instance;}
}
2.3.2 适用场景

适用于需要延迟加载单例对象且需要确保多线程安全的场景,如电商系统中的订单处理引擎。

2.3.3 优缺点

优点

  • 线程安全,避免了不必要的同步开销。

缺点

  • 实现复杂,可能会增加代码的可维护性难度。

2.4 静态内部类

利用Java的类加载机制,静态内部类实现单例模式既实现了延迟加载,又保证了线程安全。

2.4.1 代码示例
public class StaticInnerClassSingleton {private StaticInnerClassSingleton() {// 私有化构造函数}private static class SingletonHelper {private static final StaticInnerClassSingleton INSTANCE = new StaticInnerClassSingleton();}public static StaticInnerClassSingleton getInstance() {return SingletonHelper.INSTANCE;}
}
2.4.2 适用场景

适用于需要延迟加载但不希望增加代码复杂度的场景,如电商系统中的日志记录器(Logger)。

2.4.3 优缺点

优点

  • 延迟加载,线程安全,且实现简单。

缺点

  • 无法在实例化时传递参数。

3. 电商交易系统中的单例模式应用

在电商交易系统中,单例模式的应用场景非常广泛,以下我们将详细探讨几个实际应用场景。

3.1 配置管理器

电商系统中,各种配置如数据库连接、API密钥等,都是全局的且通常不会频繁更改。这些配置数据可以封装在一个配置管理器(Configuration Manager)类中,并使用单例模式来确保只有一个实例来管理所有配置。

3.1.1 代码示例
public class ConfigurationManager {private static ConfigurationManager instance;private Properties properties;private ConfigurationManager() {// 加载配置properties = new Properties();try {properties.load(new FileInputStream("config.properties"));} catch (IOException e) {e.printStackTrace();}}public static synchronized ConfigurationManager getInstance() {if (instance == null) {instance = new ConfigurationManager();}return instance;}public String getProperty(String key) {return properties.getProperty(key);}
}

3.2 数据库连接池

在电商系统中,数据库操作频繁且连接池是必须的。通过单例模式,可以确保数据库连接池只有一个实例,从而有效管理数据库连接资源。

3.2.1 代码示例
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.LinkedList;public class DatabaseConnectionPool {private static DatabaseConnectionPool instance;private LinkedList<Connection> pool;private DatabaseConnectionPool() {// 初始化连接池pool = new LinkedList<>();for (int i = 0; i < 10; i++) {try {pool.add(DriverManager.getConnection("jdbc:mysql://localhost:3306/ecommerce", "user", "password"));} catch (SQLException e) {e.printStackTrace();}}}public static synchronized DatabaseConnectionPool getInstance() {if (instance == null) {instance = new DatabaseConnectionPool();}return instance;}public Connection getConnection() {return pool.poll();}public void releaseConnection(Connection connection) {pool.offer(connection);}
}

3.3 日志管理器

日志记录器(Logger)在电商系统中也非常重要,特别是在处理订单、支付、库存管理等模块时,需要记录大量操作日志。通过单例模式,保证日志记录器的唯一性,使得日志输出更加一致。

3.3.1 代码示例
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;public class Logger {private static Logger instance;private PrintWriter writer;private Logger() {try {writer = new PrintWriter(new FileWriter("log.txt", true));} catch (IOException e) {e.printStackTrace();}}public static synchronized Logger getInstance() {if (instance == null) {instance = new Logger();}return instance;}public void log(String message) {writer.println(message);writer.flush();}
}
3.4 类图

在这里插入图片描述

4. 单例模式的常见问题及解决方案

4.1 序列化问题

如果单例类实现了 Serializable 接口,那么反序列化时可能会创建一个新的实例,破坏单例性。

4.1.1 解决方案

为了避免反序列化导致的单例破坏,我们可以通过实现 readResolve 方法来确保反序列化返回的始终是同一个实例。

protected Object readResolve() {return getInstance();
}

4.2 多线程环境中的双重检查锁问题

双重检查锁是用于提升性能的一个方法,但它在Java早期版本中由于内存模型的原因可能会导致问题。不过在Java 5及以后,通过使用 volatile 关键字来声明实例变量,可以确保多线程环境下的安全性。

4.3 反射攻击

反射可以访问私有构造函数,从而破坏单例模式的私有性和唯一性。

4.3.1 解决方案

可以在构造函数中添加一个条件判断,如果实例已经存在,则抛出异常。

public class Singleton {private static Singleton instance;private Singleton() {if (instance != null) {throw new IllegalStateException("Already initialized");}}public static synchronized Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}
}

5. 单例模式在开源框架中的应用

单例模式在Java开源框架中得到了广泛的应用,尤其是在那些需要管理全局状态、资源池、配置数据的场景中。以下我们将深入探讨几个常见的开源框架中,单例模式的具体应用及其重要性。

5.1 Spring框架中的单例模式

Spring框架是Java企业级开发中最流行的框架之一,其中广泛使用了单例模式来管理Bean的生命周期。在Spring中,默认情况下,Bean是以单例模式进行管理的。也就是说,对于每个Spring容器,任何一个特定的Bean在容器中只会存在一个实例。

5.1.1 Spring Bean的单例模式

在Spring中,默认的Bean作用域(scope)是单例(singleton)。这意味着在同一个Spring容器中,一个Bean的定义会返回相同的实例。这个机制不仅提高了资源的利用率,还确保了在多个地方引用同一Bean时的一致性。

代码示例
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.stereotype.Component;@Component
public class OrderService {public void processOrder() {System.out.println("Processing order...");}
}public class SpringSingletonExample {public static void main(String[] args) {ApplicationContext context = new AnnotationConfigApplicationContext("com.example");OrderService orderService1 = context.getBean(OrderService.class);OrderService orderService2 = context.getBean(OrderService.class);// 两个bean实例是相同的System.out.println(orderService1 == orderService2); // 输出: true}
}

在上面的代码中,OrderService 被声明为一个 @Component,并且默认情况下它是单例的。这意味着 orderService1orderService2 都引用同一个实例。

5.1.2 Spring中的懒加载单例

Spring还支持懒加载单例,这意味着Bean实例在第一次被请求时才会被创建,而不是在容器启动时就立即创建。这种机制可以有效地减少应用启动时的内存占用,特别是在大型应用中。

代码示例
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;@Configuration
public class AppConfig {@Bean@Lazypublic OrderService orderService() {return new OrderService();}
}

在这个示例中,OrderService Bean 被标记为 @Lazy,这意味着它不会在Spring容器启动时立即实例化,而是在第一次调用 getBean() 方法时才会被创建。

5.1.3 Spring中的单例问题与解决方案

虽然Spring默认提供了单例模式,但在某些情况下,单例模式可能会引发问题,特别是在多线程环境下。如果一个单例Bean是有状态的,且这些状态会被多个线程共享,那么就可能出现线程安全问题。

5.1.3.1 解决方案:使用无状态Bean

一种常见的解决方案是确保单例Bean是无状态的,即不包含可变的成员变量,这样可以避免线程安全问题。

@Component
public class StatelessService {public void executeTask() {// 执行无状态任务}
}

如果确实需要在单例Bean中保存状态,可以考虑使用线程安全的数据结构,或者将状态存储在ThreadLocal中,这样可以保证每个线程有自己独立的状态。

5.2 Hibernate中的单例模式

Hibernate是另一个广泛使用的Java开源框架,主要用于对象关系映射(ORM)。在Hibernate中,SessionFactory就是一个典型的单例模式应用实例。SessionFactory是一个重量级对象,在应用程序生命周期中通常只需要一个实例。

5.2.1 SessionFactory的单例实现

SessionFactory的创建过程是非常耗费资源的,因此在实际应用中通常将其设计为单例。通过单例模式,确保整个应用程序只创建一个SessionFactory实例,从而提高性能和资源利用率。

代码示例
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;public class HibernateUtil {private static SessionFactory sessionFactory;static {try {sessionFactory = new Configuration().configure().buildSessionFactory();} catch (Throwable ex) {throw new ExceptionInInitializerError(ex);}}public static SessionFactory getSessionFactory() {return sessionFactory;}
}

在上面的示例中,SessionFactory 被作为一个静态变量加载并初始化,这样整个应用中就只会有一个 SessionFactory 实例。

5.2.2 使用场景

在一个典型的电商系统中,SessionFactory 可以用来管理与数据库的所有交互。这包括所有的CRUD操作,以及事务管理。由于数据库操作频繁且资源消耗大,将SessionFactory设计为单例可以显著提高性能。

5.3 Log4j中的单例模式

Log4j是一个流行的Java日志框架,在很多Java应用中被广泛使用。Log4j的核心类 Logger 也采用了单例模式来确保日志管理的唯一性。

5.3.1 Logger的单例实现

Logger 类的单例实现确保了每个类只会有一个 Logger 实例。这样可以保证在不同的地方记录日志时,使用的是同一个日志配置,从而保证了日志输出的统一性。

代码示例
import org.apache.log4j.Logger;public class Log4jExample {private static final Logger logger = Logger.getLogger(Log4jExample.class);public static void main(String[] args) {logger.info("This is an info message");logger.error("This is an error message");}
}

在这个例子中,Logger 是通过 Logger.getLogger() 方法获取的,这个方法内部使用了单例模式来确保每个类只有一个 Logger 实例。

5.3.2 优势与应用场景

在电商系统中,日志记录是非常重要的,尤其是在处理订单、支付和库存等模块时。通过Log4j的单例 Logger 类,可以确保日志的集中管理,便于问题的追踪和分析。

5.4 JUnit中的单例模式

JUnit是Java最流行的单元测试框架之一。在JUnit 4中,使用单例模式来管理测试运行器(TestRunner)的实例。

5.4.1 TestRunner的单例实现

TestRunner负责管理测试的执行和结果收集,它的单例实现保证了在一个测试会话中,所有测试用例共享相同的TestRunner实例,从而避免了不必要的资源消耗。

代码示例
import org.junit.runner.JUnitCore;
import org.junit.runner.Result;
import org.junit.runner.notification.Failure;public class JUnitSingletonExample {private static JUnitCore junitCore;private JUnitSingletonExample() {}public static synchronized JUnitCore getInstance() {if (junitCore == null) {junitCore = new JUnitCore();}return junitCore;}public static void main(String[] args) {Result result = getInstance().run(MyTestClass.class);for (Failure failure : result.getFailures()) {System.out.println(failure.toString());}System.out.println("Success: " + result.wasSuccessful());}
}

在这个示例中,JUnitCore 被设计为单例,以便所有测试共享同一个运行器实例。

5.5 Apache Commons中的单例模式

Apache Commons是一个提供了许多实用工具类的Java库。在Apache Commons中,有一些类使用了单例模式来确保全局唯一的资源管理。

5.5.1 Singleton类的使用

Apache Commons Lang库中的 StringUtils 类就是一个典型的例子。虽然它是一个工具类,没有状态,但仍然通过单例模式提供了一些全局方法。

import org.apache.commons.lang3.StringUtils;public class CommonsSingletonExample {public static void main(String[] args) {String str = "   Hello World!   ";String trimmedStr = StringUtils.trim(str);System.out.println(trimmedStr); // 输出 "Hello World!"}
}

虽然 StringUtils 并不是真正的单例,但它的无状态设计和静态方法的使用使其可以像单例一样在全局范围内使用。

6. 结论

单例模式是Java设计模式中的一个基础模式,它在电商交易系统中的应用非常广泛。通过对不同实现方式的分析和对常见问题的探讨,我们可以更好地理解如何在实际项目中应用单例模式。

以上内容为关于Java单例模式的详尽分析和实践示范,涵盖了从基本概念到高级应用的各个方面。希望这篇文章能够帮助你在实际项目中更好地应用单例模式,提高代码质量和系统性能。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

企业产品推广系统小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;用户管理&#xff0c;活动资讯管理&#xff0c;产品分类管理&#xff0c;产品信息管理&#xff0c;用户分享管理&#xff0c;留言板管理&#xff0c;系统管理 微信端账号功能包括&#xff1a;系统首页…

【Python机器学习】NLP词频背后的含义——奇异值分解

目录 左奇异向量U 奇异值向量S 右奇异向量 SVD矩阵的方向 主题约简 奇异值分解是LSA背后的算法。我们从一个小规模的语料库开始&#xff1a; from nlpia.book.examples.ch04_catdog_lsa_sorted import lsa_models,prettify_tdmbow_svd,tfidf_svdlsa_models() print(prett…

如何消除工人们对TPM管理培训的抵触情绪?

在探讨如何消除工人们对TPM管理培训抵触情绪的问题时&#xff0c;我们首先需要深入理解这种抵触情绪的根源&#xff0c;进而设计出一套既科学又人性化的策略来逐步化解。TPM作为一种旨在通过全员参与&#xff0c;实现设备综合效率最大化的管理模式&#xff0c;其成功实施离不开…

kube-scheduler调度策略之预选策略(三)

一、概述 摘要&#xff1a;本文我们继续分析源码&#xff0c;并聚焦在预选策略的调度过程的执行。 二、正文 说明&#xff1a;基于 kubernetes v1.12.0 源码分析 上文我们说的(g *genericScheduler) Schedule()函数调用了findNodesThatFit()执行预选策略。 2.1 findNodesTha…

新手使用住宅代理有哪些常见误区?

作为新手&#xff0c;在使用住宅代理时往往会陷入一些常见误区&#xff0c;这些误区不仅可能影响到使用效果&#xff0c;甚至可能会带来安全风险。今天将与大家探讨新手在使用住宅代理时可能会遇到的几个关键误区&#xff0c;并提供相应的解决方案。误区一&#xff1a;盲目追求…

Spike-in:微生态16S扩增子绝对定量重磅上线!

16S扩增子测序是一种广泛应用于微生物群落分析的技术&#xff0c;主要用于研究环境样本中微生物的种类、丰度及其生态关系。 然而&#xff0c;传统的16S扩增子测序通常只能提供相对丰度数据&#xff0c;无法准确反映样本中各微生物的绝对数量&#xff0c;导致在一定程度上掩盖…

LACP链路聚合

链路聚合包含两种模式&#xff1a;手动负载均衡模式和LACP&#xff08;Link AggregationControl Protocol&#xff09;模式。 手工负载分担模式&#xff1a;Eth-Trunk的建立、成员接口的加入由手工配置&#xff0c;没有链路聚合控制协议的参与。该模式下所有活动链路都参与数…

如何在 MySQL 中匹配列

在 MySQL 中&#xff0c;匹配列可以通过多种方式实现&#xff0c;具体取决于你要执行的操作类型。常见的列匹配操作包括条件查询、JOIN操作、字符串匹配等。以下是具体解决的几种方式。 1、问题背景 在 MySQL 中&#xff0c;可以使用 “” 运算符来匹配列。例如&#xff1a; …

中断处理流程举例(21)

中断流程的截图&#xff1a; 下面主要就是解释这张图&#xff1a; 当中断发生之后&#xff0c;首先是硬件&#xff0c;保存&#xff23;&#xff30;&#xff33;&#xff32;到&#xff33;&#xff30;&#xff33;&#xff32;&#xff0c;设置&#xff23;&#xff30;&…

用MATLAB 画一个64QAM的星座图

由于QAM采用幅度和相位二维调制&#xff0c;其频谱效率大大提高&#xff0c;而且不同点的欧式距离也要大于调幅AM调制方式&#xff0c;QAM也是LTE和5G NR首选的调制方式&#xff0c;本期教大家画一个64QAM的星座图。 如下&#xff1a; 首先产生一个64QAM的调制数据&#xff0…

【windows】windows 如何实现 ps aux | grep xxx -c 统计某个进程数的功能?

windows 如何实现 ps aux | grep xxx -c 统计某个进程数的功能&#xff1f; 在Windows中&#xff0c;要实现类似Linux中ps aux | grep xxx -c的功能&#xff0c;即统计某个特定进程的数量&#xff0c;可以使用PowerShell或命令提示符&#xff08;cmd.exe&#xff09;来实现。 …

【学习笔记】卫星通信NTN 3GPP标准化进展分析(二)- 3GPP Release16 内容

一、引言&#xff1a; 本文来自3GPP Joern Krause, 3GPP MCC (May 14,2024) Non-Terrestrial Networks (NTN) (3gpp.org) 本文总结了NTN标准化进程以及后续的研究计划&#xff0c;是学习NTN协议的入门。 【学习笔记】卫星通信NTN 3GPP标准化进展分析&#xff08;一&#xff…

SQL-多表查询

1、多表关系 一对多、多对一&#xff1a;在多的一方建立外键&#xff0c;指向一的一方。 多对多&#xff1a;至少两个外键&#xff0c;通过中间表维护。 一对一 2、多表查询概述 3、内连接 4、外连接 5、自连接 6、联合查询 7、子查询 8、多表查询案例 # 1、多表关系 #…

【EtherCAT】运行原理

目录 1、有个兄弟提了个问题&#xff0c;如下&#xff1a; 2、EtherCAT运行原理 1、有个兄弟提了个问题&#xff0c;如下&#xff1a; “您好&#xff0c;在这篇文章中https://blog.csdn.net/qq_46211259/article/details/139824335 EtherCAT的数据区有三个子报文&#xff0c…

IP SSL证书——为IP升级加密

在数字化浪潮中&#xff0c;每一份数据传输都承载着重要信息与价值。当您的业务跨越国界&#xff0c;触及全球用户时&#xff0c;确保通信安全、提升品牌信任度&#xff0c;成为了不可或缺的一环。IP SSL证书&#xff0c;作为网络安全的守护者&#xff0c;正以其独特的优势&…

【达梦数据库】DBeaver连接达梦数据库

打开 DBeaver&#xff0c;新建驱动管理器 新建驱动管理器&#xff0c;配置信息如下 添加库文件&#xff0c;jar包使用项目上使用的jdbc驱动包即可&#xff0c;找到本地maven仓库jar位置进行添加。 <dependency><groupId>com.dameng</groupId><artifact…

NLP从零开始------文本中阶序列处理之语言模型(完整版)

语言模型( language model) 用于计算一个文字序列的概率&#xff0c; 评估该序列作为一段文本出现在通用或者特定场景中的可能性。每个人的语言能力蕴涵了一个语言模型&#xff0c;当我们说出或写下一段话的时候&#xff0c;已经在不自觉地应用语言模型来帮助我们决定这段话中的…

【C++】vector(下)--下篇

个人主页~ vector&#xff08;上&#xff09;~ vector&#xff08;下&#xff09;–上篇~ vector 二、模拟实现3、test.cpptest1test2test3test4test5test6 三、一个难题 二、模拟实现 3、test.cpp test1 这个没啥好说的&#xff0c;就是尾插和迭代器都能正常使用 //测尾…

微电网光储充用什么电能表?

背景 在可再生能源的需求不断增加&#xff0c;以及能源转型的推进&#xff0c;储能技术的重要性日益凸显。储能计量表作为储能系统的关键组成部分&#xff0c;对于监测、评估和管理储能系统性能具有重要作用。 在新能源发电领域&#xff0c;如分布式光伏、风电等&#xff0c;…

面相对象的成员介绍

2.面相对象的成员 -> 类: a.类的定义&#xff1a; 1.类是对公共特点的抽象&#xff0c;其中包含了很多成员&#xff0c;如属性&#xff08;成员变量 &#xff09;、方法、构造器等.要想很好的定义类&#xff0c;就必须要好好的了解这些类的成员 b.访问修饰符 控制属性的…