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

单例模式的写法(保证线程安全)

1. 引言

1.1 什么是单例模式?

单例模式(Singleton Pattern)是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点
核心思想:控制实例化过程,避免重复创建对象。

1.2 为什么需要单例模式?

  • 节省资源:某些对象(如数据库连接池、日志管理器)只需要一个实例。

  • 全局访问:方便在多个模块中共享数据。

  • 避免冲突:如配置文件管理,防止多个实例修改导致不一致。

1.3 线程安全的重要性

  • 多线程环境下,如果不加控制,可能导致:

    • 创建多个实例(违反单例原则)。

    • 对象状态不一致(如缓存数据被覆盖)。

  • 目标:确保在任何情况下,单例类只被初始化一次


2. 单例模式的实现方式

2.1 饿汉式(Eager Initialization)

代码实现
public class Singleton {
    // 类加载时就初始化(线程安全由JVM保证)
    private static final Singleton INSTANCE = new Singleton();
    
    // 私有构造方法,防止外部new
    private Singleton() {}
    
    // 全局访问点
    public static Singleton getInstance() {
        return INSTANCE;
    }
}
特点
  • 优点

    • 实现简单,线程安全(由类加载机制保证)。

    • 没有锁,性能高。

  • 缺点

    • 即使不用也会创建实例,可能浪费内存。

    • 无法传递参数初始化(如需要动态配置)。

适用场景
  • 实例占用内存小,且一定会被使用(如简单配置类)。


2.2 懒汉式(Lazy Initialization)

基础版(非线程安全)
public class Singleton {
    private static Singleton instance;
    
    private Singleton() {}
    
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton(); // 多线程下可能创建多个实例
        }
        return instance;
    }
}

问题:多线程环境下,多个线程可能同时进入 if (instance == null),导致多次实例化。


加锁版(线程安全但低效)
public class Singleton {
    private static Singleton instance;
    
    private Singleton() {}
    
    // 方法加锁,保证线程安全
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

问题:每次调用 getInstance() 都会加锁,性能差(99%的情况不需要同步)。


2.3 双重检查锁(Double-Checked Locking, 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;
    }
}
关键点
  1. volatile 的作用

    • 防止指令重排序(避免返回未初始化的对象)。

    • 保证可见性(一个线程修改后,其他线程立即可见)。

  2. 两次判空

    • 第一次检查(无锁):提高性能,避免每次加锁。

    • 第二次检查(加锁):防止多线程竞争导致多次实例化。

优点
  • 线程安全 + 高性能(只有第一次初始化需要同步)。

缺点
  • 代码稍复杂,需理解 volatile 和指令重排序。


2.4 静态内部类(Holder Class)

代码实现
public class Singleton {
    private Singleton() {}
    
    // 静态内部类(延迟加载)
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
    
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE; // 调用时才加载
    }
}
原理
  • 类加载机制SingletonHolder 只有在 getInstance() 被调用时才会加载。

  • 线程安全:由 JVM 保证静态内部类的初始化是线程安全的。

优点
  • 线程安全 + 延迟加载 + 无锁高性能。

  • 比双重检查锁更简洁。

缺点
  • 无法传递参数初始化(适合无参构造)。


2.5 枚举(Enum Singleton)

代码实现
public enum Singleton {
    INSTANCE; // 单例实例
    
    public void doSomething() {
        System.out.println("Singleton method");
    }
}
原理
  • JVM 保证枚举唯一性:枚举实例在类加载时初始化,且不可反射创建。

优点
  1. 绝对线程安全(JVM 保证)。

  2. 防止反射攻击(无法通过反射创建新实例)。

  3. 防止反序列化破坏(枚举天然支持 readResolve)。

缺点
  • 不能延迟加载(类加载时就初始化)。

  • 写法稍特殊(部分开发者不习惯)。


3. 如何选择单例模式?

实现方式线程安全延迟加载防止反射防止反序列化性能适用场景
饿汉式⭐⭐⭐简单场景
懒汉式(同步)不推荐
双重检查锁⭐⭐⭐高并发
静态内部类⭐⭐⭐推荐
枚举⭐⭐⭐最佳实践

推荐选择

  1. 简单场景 → 饿汉式。

  2. 需要延迟加载 → 静态内部类。

  3. 高并发 + 参数初始化 → 双重检查锁。

  4. 最佳实践 → 枚举单例(安全、简洁)。


4. 单例模式的破坏与防御

4.1 反射攻击

问题
Singleton instance1 = Singleton.getInstance();
// 反射调用私有构造方法
Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
Singleton instance2 = constructor.newInstance(); // 破坏单例
防御(非枚举类)
private Singleton() {
    if (INSTANCE != null) {
        throw new RuntimeException("Use getInstance() method!");
    }
}

枚举天然防御反射(JVM 保证唯一性)。


4.2 反序列化破坏

问题
// 序列化
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton.ser"));
oos.writeObject(Singleton.getInstance());
// 反序列化(会调用readObject,可能创建新实例)
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("singleton.ser"));
Singleton instance2 = (Singleton) ois.readObject(); // 可能破坏单例
防御(非枚举类)
// 添加readResolve方法,返回单例实例
protected Object readResolve() {
    return getInstance();
}

枚举天然防御反序列化(JVM 保证唯一性)。


5. 总结

  1. 线程安全是关键:多线程环境下必须保证单例唯一。

  2. 推荐实现

    • 枚举单例(简洁、安全、防反射)。

    • 静态内部类(延迟加载、高性能)。

    • 双重检查锁(适合需要参数初始化的场景)。

  3. 避免

    • 不加锁的懒汉式(线程不安全)。

    • 方法级同步懒汉式(性能差)。

相关文章:

  • 【GeoDa使用】空间自相关分析操作
  • 数据清洗
  • Java学习-异常抛出及自定义
  • Go语言从零构建SQL数据库(5)-Pratt解析算法:SQL表达式解析的核心引擎
  • 第一个简易SSM框架项目
  • 浙江大学DeepSeek系列专题线上公开课第二季第四期即将上线!端云协同:让AI更懂你的小心思! - 张圣宇 研究员
  • TuGraph图数据库使用尝试过程(图文讲解)
  • java: 找不到符号 符号: 变量 log
  • C语言的泛型函数,可以模拟C++的模版效果
  • C++(初阶)(十)——vector模拟实现
  • 什么是RACI矩阵,应用在什么场景?
  • R语言空间水文气象数据分析:从插值到动态可视化
  • CUDA 与 OpenCL 对比
  • Porting Layer - 跨平台函数接口封装(RTOS/Windows)- C语言
  • 智慧医疗数据集
  • 跨境多商户供销电商系统:三位一体生态,赋能企业全球化无界合作
  • 在MDK新版本中添加AC5编译器支持
  • 演员徐梓辰正式加入创星演员出道计划,开启演艺新纪元
  • vmware、centos: 快照、redis集群克隆、启动异常
  • 流程控制语句练习题总结
  • 荥阳网站建设/百度一下百度搜索入口
  • 怎样用xampp做网站/智推教育seo课程
  • 申请个网站/郑州网站排名优化公司
  • 百度不收录网站首页/创建网站平台
  • 做网站的三个软件/北京seo运营
  • 相亲网站建设关键/微信广告推广价格表