找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

300

积分

0

好友

36

主题
发表于 2025-12-26 09:20:12 | 查看: 28| 回复: 0

单例模式(Singleton Pattern)是一种创建型设计模式,其主要目的是确保一个类只有一个实例,并提供一个全局访问点。这在需要控制资源(如数据库连接池、配置管理、日志对象)或保证业务唯一性(如序列号生成器)的场景中非常有用。

单例模式的核心特点

实现一个单例模式通常需要满足以下三个核心条件:

  1. 私有化构造方法:防止外部通过 new 关键字直接创建实例。
  2. 私有化静态实例引用:在类内部持有该唯一实例的引用。
  3. 公有化静态获取方法:提供一个全局的静态方法来获取这个唯一实例。

为什么需要特别注意实现方式?

仅满足上述三点基础的实现可能并不可靠,主要面临两个挑战:

  1. 反射攻击:通过 java.lang.reflect.AccessibleObject.setAccessible 方法可以绕过私有构造器的限制。
  2. 序列化与反序列化:一个已被序列化的单例对象,在反序列化时可能会生成新的实例,破坏单例性。

下面我们将详细解析几种常见的单例实现方式,并分析其优缺点。

懒汉式 (Lazy Initialization)

懒汉式的特点是实例在第一次被使用时才创建,延迟加载可以节约资源。

1. 基础懒汉式(线程不安全)

这是最简单的形式,但在多线程环境下会创建多个实例。

基础懒汉式代码示例

public class Singleton {
    private static Singleton instance;
    private Singleton() {}
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

问题:当多个线程同时执行到 if (instance == null) 判断时,可能都会进入条件内,从而创建多个实例。

2. 同步方法懒汉式(线程安全但低效)

通过给获取方法加 synchronized 关键字来实现线程安全。

同步方法懒汉式代码

public static synchronized Singleton getInstance() {
    if (instance == null) {
        instance = new Singleton();
    }
    return instance;
}

缺点:虽然保证了线程安全,但每次调用 getInstance() 都需要进行同步,性能开销较大。

3. 双重检查锁定 (Double-Checked Locking, DCL)

为了兼顾线程安全和性能,双重检查锁定模式应运而生。

双重检查锁定代码

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

原理:第一次检查避免不必要的同步,第二次检查在同步块内确保只有一个线程创建实例。但它仍然有一个潜在问题:instance = new Singleton() 这行代码并非原子操作,在Java内存模型中可能发生指令重排序,导致其他线程获取到一个未初始化完成的对象。

解决方案:使用 volatile 关键字修饰实例变量,禁止指令重排序。

volatile关键字修饰实例

private volatile static Singleton instance;

4. 静态内部类实现(推荐)

这是一种更优雅的懒汉式实现,利用了类加载机制来保证初始化实例时只有一个线程,且无需同步开销。

静态内部类实现单例

private Singleton() {}
private static class SingletonHolder {
    private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
    return SingletonHolder.INSTANCE;
}

优点:线程安全,延迟加载,效率高。因为 SingletonHolder 是私有的,只有在 getInstance() 方法被调用时才会加载并初始化 INSTANCE

饿汉式 (Eager Initialization)

饿汉式的特点是类加载时就初始化实例,基于JVM的类加载机制,本身就是线程安全的。

饿汉式代码示例

private static final Singleton instance = new Singleton();
private Singleton(){}
public static Singleton getInstance() {
    return instance;
}

优点:实现简单,线程安全。
缺点:无论是否用到,类装载时就完成实例化,如果该实例很耗资源且从未使用,会造成内存浪费。

枚举式 (Enum Singleton) - 《Effective Java》推荐

使用枚举是实现单例模式的最佳方式之一,它能天然防御反射攻击和序列化问题。

枚举实现单例

enum SingletonEnum {
    INSTANCE;
    // 可以在此添加实例方法
    public void doSomething() {
        // ...
    }
}

访问方式SingletonEnum.INSTANCE

优势

  1. 线程安全:枚举实例的创建是线程安全的。
  2. 防止反序列化创建新对象:JVM保证每个枚举类型及其定义的枚举常量在全局都是唯一的,在序列化和反序列化过程中不会产生新的实例。
  3. 防止反射攻击:JDK源码中明确禁止使用反射创建枚举实例。

总结与选择建议

实现方式 线程安全 延迟加载 防止反射/序列化破坏 性能 实现复杂度
基础懒汉式 简单
同步方法懒汉式 简单
双重检查锁定(DCL) 较高 中等
静态内部类 简单
饿汉式 简单
枚举 ❌/✅* 简单

*枚举的实例在类加载时被创建,可视为一种“饿汉式”。但其在访问时才触发类加载的特性,在某些情况下也可达到类似延迟加载的效果。

并发编程场景下,如果需要延迟加载,静态内部类是实现单例的优选方案。如果对资源消耗不敏感,简单的饿汉式也是不错的选择。而在现代Java开发中,枚举因其简洁性和绝对的安全性,被广泛认为是实现单例模式的最佳实践。




上一篇:深入剖析MySQL事务隔离级别:并发控制与ACID特性详解
下一篇:Java AES-256加密遇到InvalidKeyException?JDK策略文件替换与配置详解
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-11 14:48 , Processed in 0.250947 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表