【设计模式】创建型-单例模式

文章目录


前言

在AI时代,代码的编写可以被大模型辅助甚至替代,但程序员真正的核心竞争力是技术思维!设计模式这类沉淀了数十年的"内功心法",决定了代码的可维护性、扩展性和稳定性,是AI无法完全替代的核心能力。单例模式作为创建型模式的入门典范,看似简单却暗藏细节,是每个Java开发者必须吃透的基础。

一、概念

单例模式(Singleton Pattern)是一种创建型设计模式,核心目标是保证一个类在整个应用程序中只有一个实例,并提供一个全局访问点。它通过控制对象的创建过程,避免重复实例化导致的资源浪费(如数据库连接池、配置类),同时确保全局状态的一致性。

二、核心思想

  1. 私有化构造方法 :禁止外部通过new关键字创建实例,从根源上杜绝多实例;
  2. 私有静态实例:在类内部维护唯一的实例对象;
  3. 公共静态方法:提供全局访问入口,确保外部只能通过该方法获取实例。

单例模式的核心本质是控制实例的生命周期,将对象的创建权收归类自身,而非外部调用者。

三、常见实现方式(Java代码)

单例模式有多种实现方式,不同方式对应不同的线程安全、性能和初始化策略,以下是最常用的4种:

1. 饿汉式(立即加载)

java 复制代码
/**
 * 饿汉式单例:类加载时直接初始化实例,线程安全
 * 优点:实现简单、天然线程安全;缺点:类加载时就创建实例,可能造成资源浪费
 */
public class HungrySingleton {
    // 1. 私有静态最终实例(类加载时初始化,JVM保证线程安全)
    private static final HungrySingleton INSTANCE = new HungrySingleton();
    
    // 2. 私有化构造方法,禁止外部new
    private HungrySingleton() {
        // 防止反射破坏单例(可选增强)
        if (INSTANCE != null) {
            throw new RuntimeException("禁止重复创建单例实例");
        }
    }
    
    // 3. 公共静态方法,提供全局访问点
    public static HungrySingleton getInstance() {
        return INSTANCE;
    }
    
    // 测试方法
    public void sayHello() {
        System.out.println("饿汉式单例:" + this.hashCode());
    }
}

2. 懒汉式(延迟加载,线程不安全)

java 复制代码
/**
 * 懒汉式单例:首次调用时初始化实例,基础版本线程不安全
 * 优点:延迟加载,节省资源;缺点:多线程下可能创建多个实例
 */
public class LazySingleton {
    // 1. 私有静态实例(未初始化)
    private static LazySingleton INSTANCE;
    
    // 2. 私有化构造方法
    private LazySingleton() {}
    
    // 3. 公共静态方法,首次调用时创建实例
    public static LazySingleton getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new LazySingleton();
        }
        return INSTANCE;
    }
    
    public void sayHello() {
        System.out.println("懒汉式单例:" + this.hashCode());
    }
}

3. 懒汉式(同步方法,线程安全)

java 复制代码
/**
 * 懒汉式单例:同步方法保证线程安全
 * 优点:线程安全、延迟加载;缺点:synchronized锁粒度大,性能低
 */
public class SyncLazySingleton {
    private static SyncLazySingleton INSTANCE;
    
    private SyncLazySingleton() {}
    
    // 加synchronized关键字,保证多线程下只有一个线程进入
    public static synchronized SyncLazySingleton getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new SyncLazySingleton();
        }
        return INSTANCE;
    }
    
    public void sayHello() {
        System.out.println("同步懒汉式单例:" + this.hashCode());
    }
}

4. 双重检查锁(DCL,推荐)

java 复制代码
/**
 * 双重检查锁(DCL)单例:兼顾延迟加载、线程安全和性能
 * 优点:线程安全、延迟加载、性能高;缺点:实现稍复杂,需注意volatile关键字
 */
public class DCLSingleton {
    // volatile关键字:禁止指令重排,保证INSTANCE初始化完成后再被读取
    private static volatile DCLSingleton INSTANCE;
    
    private DCLSingleton() {}
    
    public static DCLSingleton getInstance() {
        // 第一层检查:避免每次调用都加锁
        if (INSTANCE == null) {
            // 加锁:保证只有一个线程进入初始化流程
            synchronized (DCLSingleton.class) {
                // 第二层检查:防止多个线程等待锁后重复创建
                if (INSTANCE == null) {
                    INSTANCE = new DCLSingleton();
                }
            }
        }
        return INSTANCE;
    }
    
    public void sayHello() {
        System.out.println("DCL单例:" + this.hashCode());
    }
}

5. 枚举单例(最优解)

java 复制代码
/**
 * 枚举单例:JVM天然保证单例,避免反射和序列化破坏
 * 优点:实现最简单、绝对线程安全、防反射/序列化;缺点:无法延迟加载
 */
public enum EnumSingleton {
    // 唯一实例
    INSTANCE;
    
    public void sayHello() {
        System.out.println("枚举单例:" + this.hashCode());
    }
}

6. 测试类

java 复制代码
public class SingletonTest {
    public static void main(String[] args) {
        // 饿汉式测试
        HungrySingleton hungry1 = HungrySingleton.getInstance();
        HungrySingleton hungry2 = HungrySingleton.getInstance();
        System.out.println(hungry1 == hungry2); // true
        
        // DCL测试
        DCLSingleton dcl1 = DCLSingleton.getInstance();
        DCLSingleton dcl2 = DCLSingleton.getInstance();
        System.out.println(dcl1 == dcl2); // true
        
        // 枚举测试
        EnumSingleton enum1 = EnumSingleton.INSTANCE;
        EnumSingleton enum2 = EnumSingleton.INSTANCE;
        System.out.println(enum1 == enum2); // true
    }
}

四、优缺点

1. 优点

  1. 节省资源:避免重复创建对象,减少内存占用和GC压力(如数据库连接池、配置类);
  2. 全局一致性:确保全局只有一个实例,避免多实例导致的状态不一致;
  3. 简化访问:提供统一的全局访问点,无需频繁传递对象引用。

2. 缺点

  1. 违背单一职责原则:单例类既负责业务逻辑,又负责控制实例创建;
  2. 扩展性差:单例模式本质是"硬编码"的全局变量,若后续需要多实例,修改成本高;
  3. 测试困难:单例依赖难以模拟,单元测试时无法灵活替换实例;
  4. 可能引发内存泄漏:单例实例长期存活,若持有外部引用(如Context),可能导致内存泄漏。

五、应用场景

单例模式适用于需要全局唯一实例、创建成本高、无需多实例的场景:

  1. 工具类/配置类 :如Spring中的Environment、项目中的全局配置类;
  2. 资源管理类:数据库连接池、Redis连接池、线程池(避免重复创建连接/线程);
  3. 日志类:全局日志管理器,确保日志写入的一致性;
  4. 缓存类:全局缓存实例,避免多缓存实例导致数据不一致;
  5. 框架核心类 :Spring容器中的Bean默认单例、Java的Runtime类(Runtime.getRuntime())。

六、注意事项

  1. 线程安全 :懒汉式基础版本在多线程下会创建多个实例,必须通过synchronized或DCL保证线程安全;
  2. 反射破坏:可通过在构造方法中检查实例是否已存在,防止反射创建多实例;
  3. 序列化破坏 :实现Serializable接口时,需重写readResolve()方法,返回唯一实例;
  4. 延迟加载:若实例创建成本高且不一定被使用,优先选择DCL或懒汉式;若实例必须立即初始化,选择饿汉式或枚举。

总结

单例模式是最简单的设计模式之一,却体现了"控制对象创建"的核心设计思想。在AI时代,掌握单例模式不仅是写出"正确代码",更是理解"为何这么设计"------这正是程序员区别于AI的核心竞争力。选择单例模式时,需根据场景权衡延迟加载、线程安全和性能,优先推荐枚举单例(简单安全)或DCL(兼顾性能)。

相关推荐
我爱学习_zwj2 小时前
设计模式-3(装饰器模式)
前端·设计模式·装饰器模式
魑魅魍魉都是鬼2 小时前
Android:java kotlin 单例模式
android·java·单例模式
biter down16 小时前
C++ 单例模式:饿汉与懒汉模式
开发语言·c++·单例模式
文心快码BaiduComate17 小时前
Comate内置模型已支持 MiniMax-M2.7!
设计模式·程序员·前端框架
console.log('npc')18 小时前
Cursor,Trae,Claude Code如何协作生产出一套前后台app?
前端·人工智能·react.js·设计模式·ai·langchain·ai编程
czxyvX1 天前
C++ - 基于多设计模式下的同步&异步日志系统
c++·设计模式
mingshili1 天前
[架构设计] 依赖注入优于单例模式
单例模式·架构设计
一只大袋鼠1 天前
并发编程(二十三):单例模式(二):静态/非静态方法:单例内存优化关键
java·单例模式·并发编程
一叶飘零_sweeeet1 天前
volatile 关键字深度拆解:从内存屏障底层到单例模式的工业级架构设计
单例模式·volatile