23种设计模式--#2单例模式

发布于:2025-07-18 ⋅ 阅读:(18) ⋅ 点赞:(0)

一、简介

1. 什么是单例模式

        单例模式是设计模式中创建型模式的一种,它的核心思想是保证一个类在整个应用程序的生命周期中,只存在一个实例对象,并且这个实例对象能够被系统中的其他组件统一访问。就像现实生活中一个国家只有一个首都,一个公司只有一个 CEO 一样,在软件系统中,某些类的对象也只需要存在一个,以避免重复创建对象造成的资源浪费,或是多个实例同时存在导致的状态不一致等问题。

        单例模式看似简单,却是实际开发中应用非常广泛的设计模式之一。它通过对类的实例化过程进行严格控制,确保无论在什么情况下,都只能通过特定的方式获取到该类的唯一实例,从而为系统的稳定性和高效性提供保障。

2. 单例模式的核心特点

单例模式的核心特点可以概括为以下三点:

        确保唯一实例:这是单例模式最核心的特性。单例类会通过内部机制阻止外部通过常规的构造方法创建多个实例,保证在整个系统运行过程中,该类始终只有一个实例存在。例如在多线程环境下,无论多少个线程尝试获取该类的实例,最终得到的都是同一个对象,避免了因多实例存在而引发的资源竞争、状态混乱等问题。

        自行实例化:单例类不会依赖外部类来创建自身的实例,而是由类自身负责完成实例的创建。这种 “自给自足” 的特性使得单例类的实例化过程更加可控,减少了外部因素对实例创建的干扰。具体来说,单例类会在内部定义一个静态的实例对象,并通过特定的逻辑在合适的时机完成初始化,不需要外部通过new关键字或其他创建对象的方式来生成实例。

        向整个系统提供实例:单例类会提供一个全局访问点,让系统中的其他类或模块都能方便地获取到该唯一实例。这个访问点通常是一个静态的方法,例如getInstance(),外部通过调用这个方法就能得到单例对象,无需关心实例的创建细节。这种全局可访问的特性,使得单例对象可以作为系统中的 “全局工具”,承担起全局配置管理、跨模块数据共享等职责。

3.适用场景

        单例模式适用于需要确保系统中某个类只有一个实例的场景,例如配置管理器、日志记录器、数据库连接池或线程池等,这些场景中多个实例可能导致资源浪费、数据不一致或性能问题,通过提供一个全局访问点实现实例的复用和统一管理。

二、单例模式的实现方式

单例模式的实现有多种方式

饿汉式:类加载就会导致该单实例对象被创建。
​懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建。

1、懒汉式,线程不安全

这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程

优点

  • 延迟加载:实例仅在首次调用getInstance()方法时创建,避免了类加载时的资源浪费,适合实例初始化成本较高的场景。

缺点

  • 线程不安全:在多线程环境下,若多个线程同时进入if (instance == null)判断,可能会创建多个实例,破坏单例的唯一性。例如线程 A 和线程 B 同时检测到instance为 null,线程 A 先创建实例,线程 B 未感知到已创建的实例,会再次创建新实例。
public class Singleton {  
    private static Singleton instance;  
    private Singleton (){}  
  
    public static Singleton getInstance() {  
        if (instance == null) {  
            instance = new Singleton();  
        }  
        return instance;  
    }  
}
2、懒汉式,线程安全

能够在多线程中很好的工作,但是,效率很低

优点

  • 线程安全:通过synchronized关键字保证了多线程环境下只有一个线程能进入getInstance()方法,避免了多实例问题。
  • 延迟加载:保留了懒汉式延迟初始化的特性,仅在首次使用时创建实例。

缺点

  • 性能较差:每次调用getInstance()方法都需要获取同步锁,即使实例已经创建,后续所有调用仍需等待锁释放,在高并发场景下会显著影响性能。
public class Singleton {  
    private static Singleton instance;  
    private Singleton (){}  
    public static synchronized Singleton getInstance() {  
        if (instance == null) {  
            instance = new Singleton();  
        }  
        return instance;  
    }  
}
3、饿汉式

这种方式比较常用,但容易产生垃圾对象。

优点

  • 线程安全:由于实例在类加载时就已创建,且 Java 类加载机制保证了线程安全性,因此无需担心多线程环境下的实例唯一性问题。
  • 实现简单:代码逻辑清晰,无需复杂的同步或延迟加载逻辑。

缺点

  • 可能造成资源浪费:无论该实例是否被使用,都会在类加载时初始化,若实例占用大量资源(如内存、数据库连接等),且程序运行过程中始终未使用,则会造成不必要的资源消耗。
public class Singleton {  
    private static Singleton instance = new Singleton();  
    private Singleton (){}  
    public static Singleton getInstance() {  
    return instance;  
    }  
}
4、双检锁/双重校验锁

双重检查锁定(Double-Checked Locking)是对同步方法的优化,通过减少同步范围提升性能,同时保证线程安全。

public class Singleton {  
    private volatile static Singleton singleton;  
    private Singleton (){}  
    public static Singleton getSingleton() {  
    if (singleton == null) {  
        synchronized (Singleton.class) {  
            if (singleton == null) {  
                singleton = new Singleton();  
            }  
        }  
    }  
    return singleton;  
    }  
}

优点

  • 线程安全:通过双重检查和同步块,确保多线程环境下仅创建一个实例。
  • 延迟加载:实例在首次调用时初始化,避免资源浪费。
  • 性能较好:仅在实例未初始化时进行同步,实例创建后无需进入同步块,大幅减少了锁竞争。

注意事项

  • 必须使用volatile关键字修饰实例变量:instance = new DoubleCheckedLockingSingleton()可分解为三步(分配内存、初始化对象、将引用指向内存),若不加volatile,可能因指令重排序导致其他线程获取到未初始化的实例。volatile可禁止指令重排序,保证实例初始化完成后才被其他线程可见。

5. 静态内部类

静态内部类实现方式利用了 Java 类加载机制的特性,兼顾了延迟加载和线程安全,且无需显式同步。

public class StaticInnerClassSingleton {
    // 私有构造方法
    private StaticInnerClassSingleton() {}
    
    // 静态内部类,仅在被调用时才会加载
    private static class InnerClass {
        private static final StaticInnerClassSingleton instance = new StaticInnerClassSingleton();
    }
    
    // 公共访问方法,通过内部类获取实例
    public static StaticInnerClassSingleton getInstance() {
        return InnerClass.instance;
    }
}

优点

  • 线程安全:Java 类加载机制保证了静态内部类的加载过程是线程安全的,因此实例初始化过程不会出现多线程竞争。
  • 延迟加载:静态内部类InnerClass仅在getInstance()方法被调用时才会加载,实现了实例的延迟初始化。
  • 性能好:无需同步机制,避免了锁带来的性能损耗。

原理分析

  • 外部类StaticInnerClassSingleton加载时,内部类InnerClass不会被加载,因此实例不会被初始化。
  • 当调用getInstance()时,InnerClass被加载,其静态变量instance被初始化,且类加载过程由 JVM 保证线程安全,因此只会创建一个实例。

6、枚举

枚举是 Java 5 引入的特性,用枚举实现单例是一种简洁且安全的方式,由 JVM 天然保证单例特性。

public enum EnumSingleton {
    // 枚举实例,全局唯一
    INSTANCE;
    
    // 枚举类可以定义其他方法
    public void doSomething() {
        // 业务逻辑
    }
}

优点

  • 线程安全:JVM 保证枚举实例的创建是线程安全的,且在任何情况下都只会有一个实例。
  • 防止反射和序列化破坏:枚举的构造方法由 JVM 控制,反射无法通过newInstance()创建实例;序列化时枚举实例不会被重新创建,反序列化后仍为原实例。
  • 简洁性:代码量极少,无需手动处理同步、延迟加载等问题,可读性高。

适用场景

  • 适合对安全性要求高的场景,如全局配置、核心工具类等。
  • 由于枚举类型本身的特性,不适合需要继承的场景(枚举类默认继承Enum,Java 不支持多继承)。

网站公告

今日签到

点亮在社区的每一天
去签到