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

【23种设计模式】单例模式:理论剖析与 Java 实践

@[toc]

单例模式:理论剖析与 Java 实践

一、单例模式概述

单例模式是一种创建型设计模式,其核心目的在于确保一个类仅有一个实例,并提供一个全局访问点来获取该实例。这种模式在许多场景中都具有重要应用价值,例如在数据库连接池管理中,只需要一个共享的连接池实例来处理所有数据库请求,避免资源的过度消耗和冲突;在日志记录系统中,单例的日志记录器可以确保所有日志信息都按照统一的方式进行处理和存储。

二、单例模式的实现方式

(一)饿汉式单例

  • 实现思路:在类加载时就立即创建单例实例,并且在整个生命周期内都不会再创建新的实例。这种方式是基于类加载机制的特性,保证了实例的唯一性。
  • 代码示例
public class Singleton {
    // 私有静态成员变量,在类加载时就初始化实例
    private static final Singleton instance = new Singleton();

    // 私有构造函数,防止外部通过 new 关键字创建实例
    private Singleton() {}

    // 公共静态方法,用于获取单例实例
    public static Singleton getInstance() {
        return instance;
    }
}
  • 流程图
开始
|
|-- 类加载
|   |
|   |-- 创建 Singleton 实例
|
|-- 调用 getInstance 方法
|   |
|   |-- 返回已创建的实例
结束

(二)懒汉式单例(非线程安全)

  • 实现思路:在首次调用获取实例的方法时才创建单例实例,这种方式实现了延迟加载,在一定程度上节省了资源。但在多线程环境下,可能会出现多个线程同时判断实例为空并创建多个实例的问题,所以是非线程安全的。
  • 代码示例
public class Singleton {
    // 私有静态成员变量,初始化为 null
    private static Singleton instance;

    // 私有构造函数
    private Singleton() {}

    // 公共静态方法获取实例
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
  • 流程图
开始
|
|-- 调用 getInstance 方法
|   |
|   |-- 判断 instance 是否为空
|   |   |
|   |   |-- 是,创建 Singleton 实例并返回
|   |   |
|   |   |-- 否,直接返回 instance
结束

(三)懒汉式单例(线程安全,使用 synchronized 关键字)

  • 实现思路:在获取实例的方法上添加 synchronized 关键字,使得在多线程环境下,同一时间只有一个线程能够进入该方法创建实例,从而保证了线程安全。但这种方式在高并发场景下性能较差,因为每次获取实例都需要进行同步操作。
  • 代码示例
public class Singleton {
    private static Singleton instance;

    private Singleton() {}

    // 使用 synchronized 关键字保证线程安全
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
  • 流程图
开始
|
|-- 线程调用 getInstance 方法
|   |
|   |-- 获取方法锁
|   |   |
|   |   |-- 判断 instance 是否为空
|   |   |   |
|   |   |   |-- 是,创建 Singleton 实例并返回
|   |   |   |
|   |   |   |-- 否,直接返回 instance
|   |   |
|   |   |-- 释放方法锁
结束

(四)双重检查锁定(DCL)单例模式

  • 实现思路:结合了懒汉式的延迟加载特性和一定程度的性能优化。首先检查实例是否已经被创建,如果没有则进入同步块再次检查实例是否为空,然后才创建实例。这样在多数情况下,不需要进行同步操作,提高了性能,同时又保证了线程安全。
  • 代码示例
public class Singleton {
    // 使用 volatile 关键字保证可见性和禁止指令重排
    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;
    }
}
  • 流程图
开始
|
|-- 线程调用 getInstance 方法
|   |
|   |-- 判断 instance 是否为空
|   |   |
|   |   |-- 是,获取类锁
|   |   |   |
|   |   |   |-- 再次判断 instance 是否为空
|   |   |   |   |
|   |   |   |   |-- 是,创建 Singleton 实例并释放类锁,返回 instance
|   |   |   |   |
|   |   |   |   |-- 否,释放类锁,返回 instance
|   |   |
|   |   |-- 否,直接返回 instance
结束

(五)静态内部类单例模式

  • 实现思路:利用了 Java 类加载机制的特性,当外部类被加载时,内部类不会立即被加载。只有当调用 getInstance 方法时,才会加载内部类并创建单例实例。这种方式既实现了延迟加载,又保证了线程安全。
  • 代码示例
public class Singleton {
    // 私有构造函数
    private Singleton() {}

    // 静态内部类
    private static class SingletonHolder {
        // 静态成员变量,在内部类加载时创建实例
        private static final Singleton instance = new Singleton();
    }

    // 公共静态方法获取实例
    public static Singleton getInstance() {
        return SingletonHolder.instance;
    }
}
  • 流程图
开始
|
|-- 调用 getInstance 方法
|   |
|   |-- 加载 SingletonHolder 内部类
|   |   |
|   |   |-- 创建 Singleton 实例
|   |
|   |-- 返回创建的实例
结束

(六)枚举单例模式

  • 实现思路:使用枚举来实现单例模式,Java 中的枚举类型是线程安全的,并且保证了实例的唯一性。这种方式简洁明了,并且在防止反射和反序列化破坏单例方面也有天然的优势。
  • 代码示例
public enum Singleton {
    // 枚举元素,本身就是单例实例
    INSTANCE;

    // 可以添加其他方法和属性
    public void doSomething() {
        System.out.println("执行单例方法");
    }
}
  • 流程图
开始
|
|-- 调用 Singleton.INSTANCE 或相关方法
|   |
|   |-- 直接使用单例实例进行操作
结束

三、单例模式的应用场景

  • 资源共享与管理:如前面提到的数据库连接池、线程池等,通过单例模式可以确保在整个应用程序中只有一个资源管理实例,方便对资源进行统一的分配、回收和监控。
  • 全局配置信息:应用程序中的全局配置对象,例如数据库连接配置、系统参数配置等,可以设计为单例模式。这样各个模块都可以方便地获取相同的配置信息,并且在配置需要更新时,也能够统一进行处理。
  • 日志记录器:保证所有的日志信息都通过同一个日志记录器进行记录,便于日志的管理、分析和存储。

四、单例模式的优缺点

(一)优点

  • 确保实例唯一性:在整个应用程序生命周期内,只有一个单例实例存在,避免了因创建多个相同实例而导致的资源浪费和逻辑混乱,例如在多个模块需要共享同一个数据库连接时,单例模式可以保证连接的唯一性和一致性。
  • 全局访问点:提供了一个统一的全局访问点,使得其他对象可以方便地获取单例实例,提高了代码的可维护性和可读性。例如在日志记录系统中,其他模块只需调用单例日志记录器的方法即可记录日志,无需关心日志记录器的创建和管理细节。

(二)缺点

  • 违背单一职责原则:单例模式可能会将过多的功能集中在一个类中,导致这个类除了管理自身的单例实例外,还承担了其他业务逻辑,使得类的职责不够单一,不利于代码的扩展和维护。例如一个单例的数据库连接池类,除了管理连接池的创建和获取连接操作外,还可能包含了一些与数据库操作相关的辅助方法,这样当数据库连接池的管理逻辑需要修改或者扩展时,可能会影响到其他相关功能。
  • 测试困难:由于单例模式的全局唯一性,在进行单元测试时可能会遇到困难。例如在测试某个依赖单例实例的模块时,可能无法方便地替换单例实例为测试替身,从而影响测试的准确性和完整性。
  • 可能存在线程安全问题:如果在实现单例模式时没有正确处理线程安全,可能会导致在多线程环境下出现多个实例被创建的情况,破坏了单例模式的初衷,如非线程安全的懒汉式单例模式。

五、总结

单例模式在 Java 编程中是一种非常重要且常用的设计模式,它通过多种实现方式来确保一个类只有一个实例,并提供全局访问点。在实际应用中,需要根据具体的需求场景选择合适的单例实现方式,同时也要充分考虑单例模式的优缺点,合理地应用到项目开发中,以提高系统的性能、可维护性和稳定性。无论是资源管理、配置信息共享还是其他需要全局唯一实例的场景,单例模式都能够发挥其独特的作用,为构建高效、可靠的 Java 应用程序奠定基础。


http://www.kler.cn/a/421984.html

相关文章:

  • Python几种常用数据结构(重制版)
  • 【阅读记录-章节5】Build a Large Language Model (From Scratch)
  • go并发设计模式runner模式
  • Zookeeper集群数据是如何同步的?
  • Ambrus 游戏工作室将应对气候变暖与游戏变现完美结合
  • Day 32 动态规划part01
  • Java学习分享
  • 中间件之Elasticsearch
  • 各种类型无人机性能及优缺点技术详解
  • 【乐企文件生成工程】搭建docker环境,使用docker部署工程
  • 【CSS in Depth 2 精译_800】附录A:CSS 选择器的含义及部分用法示例
  • 一次奇妙的getshell之旅
  • 数据结构--二叉树的创建和遍历
  • 28.100ASK_T113-PRO Linux+QT 显示一张照片
  • 6.824/6.5840 Lab 1: MapReduce
  • 第N9周:seq2seq翻译实战-Pytorch复现-小白版
  • 量化交易系统开发-实时行情自动化交易-8.5.VNPY平台
  • 利用Docker一键发布Nginx-Tomcat-MySQL应用集群
  • 【方案三】JAVA中使用ocr(Umi-OCR)
  • 嵌入式硬件实战提升篇(三)商用量产电源设计方案 三路电源输入设计 电源管理 多输入供电自动管理 DCDC降压
  • 剖析设备像素、CSS 像素等的特性与用途
  • 关于layui的dropdown下拉框缓存问题修复
  • 【星海随笔】syslinux
  • 面试题-RocketMQ的基本架构、支持的消息模式、如何保证消息的可靠传输
  • Elasticsearch做分词实践
  • Day 30 贪心算法 part04