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

news2024/9/21 6:04:56

单例模式(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
    @Lazy
    public 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.coloradmin.cn/o/2079885.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【LeetCode Cookbook(C++ 描述)】平衡二叉树

目录 平衡二叉树基础不同插入节点方式的不同旋转LL 型失衡RR 型失衡LR 型失衡RL 型失衡 删除操作删除节点为二叉树的叶子节点删除的节点只有左子树或者右子树删除的节点既有左子树又有右子树 LeetCode #110&#xff1a;Balanced Binary Tree 平衡二叉树递归法&#xff08;自底向…

[C++番外] 抛异常

一、C语言的时候我们怎么判断错误的呢&#xff1f; C语言的错误处理机制&#xff1a; 终止程序&#xff0c;如assert&#xff0c;缺陷&#xff1a;用户难以接受。如发生内存错误&#xff0c;除0错误时就会终止程序。返回错误码&#xff0c;缺陷&#xff1a;需要程序员自己去查…

字典序排数

题目链接 字典序排数 题目描述 注意点 1 < n < 5 * 10^4 解答思路 参照题解使用dfs完成本题&#xff0c;需要注意的是结果不包含0&#xff0c;所以先遍历第一层&#xff08;1~9&#xff09;&#xff0c;再根据每个节点继续深搜&#xff0c;将访问到的节点按顺序添加…

测试开发面试题目汇总

之前因为面临换工作&#xff0c;所以通过一些渠道汇总了一些面试题目&#xff0c;然后加入了部分自己面试过程中遇到的问题&#xff0c;因此记录下来。文末有惊喜。 1. 项目经验 2. 测试的过程 3. 京东登录页面怎么测&#xff1f; 4. 如果一个普通用户&#xff0c;他的百度首…

EasyExcel动态映射Excel数据到任意实体类教程

在使用EasyExcel进行Excel导入时&#xff0c;我们经常需要将Excel中的数据映射到Java实体类中。如果Excel的列名是固定的&#xff0c;我们可以通过ExcelProperty("列名")注解直接在实体类中指定列名。但如果Excel的列名不固定&#xff0c;或者我们希望根据Excel的第一…

NS2582 同步升压双节锂电池充电管理 IC

1 特性  最大 2A 输出同步开关型升压充电器  升压效率可高达 90% 以上  内置电池短路 / 涓流 / 恒流 / 恒压模式  0.5% 电池恒压模式电压精度  支持 LED 充电状态指示  支持充电电流外部可调  支持输入适配器 DPM 功能  外置 EN 使能…

SQL语法:create、insert、update、

1.create创建表 创建表时&#xff0c;通常会有如下设置&#xff1a;主键、非空、取值唯一、使用自动增长等。 根据如图创建表名为userinfo的数据表&#xff1a; create table userinfo(id int not null primary key auto_increment,username varchar(50) not null unique,cre…

java框架第二课(Reflection反射机制)

一.关于反射 (1)使用场景介绍 平常我们写代码时&#xff0c;都是已知类名&#xff0c;类的属性&#xff0c;构造方法&#xff0c;其他方法等信息&#xff0c;然后根据类名new对象&#xff0c;这个过程称为正向操作(例如&#xff1a;有一个管理员类&#xff0c;有账号和密码属…

WEB渗透Win提权篇-BypassUAC

提权工具合集包&#xff08;免费分享&#xff09;&#xff1a; 夸克网盘分享 往期文章 WEB渗透Win提权篇-提权工具合集-CSDN博客 WEB渗透Win提权篇-RDP&Firewall-CSDN博客 WEB渗透Win提权篇-MSSQL-CSDN博客 WEB渗透Win提权篇-MYSQL-udf-CSDN博客 WEB渗透Win提权篇-Acc…

大模型基础环境部署之一:安装 Nvidia 的驱动(详细实操版)

一、系统准备前置条件 1、更新软件包列表 sudo apt-get update2、安装编译工具和依赖项 sudo apt-get install gcc sudo apt-get install make sudo apt-get install g注&#xff1a;如果在安装 g 时遇到错误消息&#xff1a;“***you do not appear to have libc header fi…

【C++ Primer Plus习题】6.2

问题: 解答: #include <iostream> #include <array> using namespace std;#define MAX 10int main() {array<float, MAX> arr;float sum0;float average0;int i 0;int count0;int bigger 0;for (i 0; i < MAX; i){cout << "请输入donation…

大数据技术之Flume 企业开发案例——聚合(7)

目录 聚合 1&#xff09;案例需求&#xff1a; 2&#xff09;需求分析 3&#xff09;实现步骤&#xff1a; 准备工作 创建 flume1-logger-flume.conf 创建 flume2-netcat-flume.conf 创建 flume3-flume-logger.conf 执行配置文件 聚合 1&#xff09;案例需求&#x…

华为eNSP:路由器子接口配置

一、拓扑图 二、 路由器配置 [R1]int g0/0/0.1#进入子接口 [R1-GigabitEthernet0/0/0.1]ip add 192.168.1.254 24#配置子接口地址 [R1-GigabitEthernet0/0/0.1]dot1q termination vid 10#标记终止的vid编号 [R1-GigabitEthernet0/0/0.1]arp broadcast enable #开启子接口的arp…

Keilv5 逻辑分析仪的使用

声明&#xff1a;基于视频【事件驱动型编程和 QP/C 框架】所做的笔记 Keilv5逻辑分析仪是Keil MDK集成开发环境&#xff08;IDE&#xff09;中的一个工具&#xff0c;用于帮助开发人员进行嵌入式系统的调试和分析。 它的作用主要有&#xff1a; 监测信号&#xff1a;Keilv5逻…

DBSCAN算法详解

1. 算法原理 DBSCAN&#xff08;Density-Based Spatial Clustering of Applications with Noise&#xff09;是一种基于密度的聚类算法&#xff0c;主要用于发现数据中的任意形状的簇&#xff0c;并能够有效地识别噪声点。它的基本思想是通过密度来定义簇&#xff0c;即在数据…

Python -- GUI图形界面编程—GUI编程实例 博主也在持续学习中[ 持续更新中!!! 欢迎白嫖 ]

本文继上篇文章http://t.csdnimg.cn/mJlmW继续介绍GUI的图形界面编程&#xff08;相关视频是哔站上的应该搜这个题目就能找到&#xff09;&#xff0c;文章还是很基础的&#xff0c;目前博主处于有一点基础的状态。 文章的主要介绍了依旧非常重要的结构tinkter库、常见的三种布…

Patch-Package:一款灵活的开源依赖修复工具

一、背景 在现代软件开发中&#xff0c;开发者通常依赖大量的开源库来加快开发进程。然而&#xff0c;随着时间的推移&#xff0c;可能会遇到一些问题&#xff1a; 开源包的缺陷&#xff1a;开源库可能存在 Bug 或者与项目不兼容的问题。开发者可以自己修复这些问题&#xff0…

QML控件: 动画输入框 LineEdit PySide6

1. 前言 本代码为扫地僧-smile原创, 废话不多说, 直接看效果图由于录制的这个GIF图掉帧严重, 实际动画效果非常细腻 2.看代码 控件模块代码如下 SmileLineEdit.qml import QtQuick import QtQuick.Controls/* __author__: 扫地僧-smile */Rectangle {// 属性property int …

这些年使用Delphi的成果

成果1&#xff1a; 收到了一件文化衫 成果2&#xff1a;被评为亚洲专家&#xff0c;收到了一套Delphi7 光碟找不到了。

Arista与英伟达IB网络竞争格局分析

悄然崛起的英伟达新对手 英伟达都有哪些对手&#xff1f; 当然首选AMD和英特尔。AMD具备AI加速卡业务&#xff0c;融合CPU和GPU设计能力&#xff1b;英特尔作为x86架构的开创者&#xff0c;如今也涉足AI加速卡领域。它们的产品在参数上与英伟达对标&#xff0c;同时在定位和售…