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

单例模式(Singleton Pattern):深入解析与应用场景

一、什么是单例模式?

单例模式(Singleton Pattern)是一种常用的软件设计模式,其核心目标是确保一个类只有一个实例,并提供一个全局访问点。这种模式在许多场景下都非常有用,可以有效地控制资源的访问和管理。

二、单例模式的实现方式

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

public class Singleton {
    private static Singleton instance;
    
    private Singleton() {}
    
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
问题示例:

假设两个线程(线程A和线程B)几乎同时调用 getInstance(),流程如下:

  1. 线程A和线程B 都检查到 if (instance == null)
    • 因为 instance 还未被初始化,所以两个线程都进入了 if 块。
  2. 线程A执行 instance = new Singleton();
    • 线程A完成了对象的创建,instance 不再为 null
  3. 线程B执行 instance = new Singleton();
    • 因为线程B在检查 instance == null 时,instance 仍然为 null(线程A的赋值操作还未被线程B感知)。
    • 线程B再次创建了一个新实例,覆盖了线程A的结果。

最终,多个线程可能创建了多个实例

2. 懒汉式(线程安全)

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

使用 synchronized 关键字确保线程安全:

3. 饿汉式(线程安全)

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

private static final Singleton INSTANCE = new Singleton(); 意味着实例是在类加载时就被创建的
● 这种方式由Java虚拟机保证线程安全,因为静态成员变量的初始化是在类加载过程中进行的,是线程安全的

4. 双重检查锁(推荐)

public class Singleton {
    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;
    }
}

"双重检查锁定”策略来保证 懒加载线程安全,同时避免不必要的同步带来的性能开销。

第一次检查:if (instance == null)
  • 当多个线程并发调用 getInstance() 时,第一次检查是否实例化了 Singleton
  • 如果 instance 已经被创建,直接返回该实例,避免进入同步代码块,从而提高性能。
同步块:synchronized (Singleton.class)
  • 如果 instance 为空,则进入同步块,确保只有一个线程可以执行实例化的代码。
  • 这个 **synchronized** 确保了当多个线程同时进入该代码块时,只有一个线程能够创建 Singleton 实例。
第二次检查:if (instance == null)
  • 线程进入同步块后,第二次检查 instance 是否为空。因为可能多个线程并发到达同步块,第一个线程会创建实例,其他线程会被阻塞。
  • 第二次检查是为了防止多个线程在同步块中创建多个实例,确保 instance 只被初始化一次。
实例化:instance = new Singleton()
  • 当第一次和第二次检查都发现 instance 为空时,创建 Singleton 实例。
为什么使用 volatile 关键字?
  • volatile 确保了 instance 的变量在多线程环境下是可见的,避免了由于 指令重排缓存 导致实例初始化出现问题。
  • 如果没有 volatile,可能会出现某些线程看到一个未完全初始化的 instance 对象,从而导致 空指针异常不一致的状态

5. 静态内部类(推荐)

public class Singleton {
    private Singleton() {}
    
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
    
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}
为什么这种方式是线程安全的?
  • 类加载机制:Java 类加载是线程安全的。当 JVM 加载类时,它会保证类的初始化过程不会出现竞争条件。因此,SingletonHolder 类在被加载时,INSTANCE 只会被初始化一次,即使在多线程环境下也能保证唯一性。
  • 懒加载:静态内部类实现的单例模式通过 类初始化时 延迟实例化 Singleton,避免了提前创建对象的开销。
  • 性能:由于没有在每次调用 getInstance() 时都加锁,避免了同步带来的性能损失。因此,这种方式在保证线程安全的同时,也具备了更高的性能。

6.使用枚举创建

public enum Singleton {
    INSTANCE;

    // 可以在这里添加需要的方法
    public void doSomething() {
        System.out.println("Doing something...");
    }
}
优点:
  1. enum定义Singleton 被定义为一个枚举类型,只有一个 INSTANCE 成员。枚举类型的实例在类加载时就会被创建,因此可以确保只有一个 INSTANCE
  2. 线程安全:Java 枚举类型天然是线程安全的,因为 Java 在枚举类型初始化时,会确保其在 JVM 中的实例是唯一且线程安全的。
  3. 防止反射破坏单例:与其他单例模式不同,枚举类型能够自动防止反射破坏单例,因为枚举类型的构造方法是 私有的,且枚举实例的生成过程由 Java 虚拟机控制。
  4. 防止序列化破坏单例:Java 序列化机制中,枚举实例会被自动处理,避免了通过反序列化重新创建实例的问题。

三、线程安全的单例实现

实现线程安全的单例模式有多种方法:

  1. 使用synchronized关键字
  2. 使用双重检查锁(volatile + synchronized)
  3. 使用静态内部类
  4. 使用枚举(Java)

四、单例模式的使用场景

1.系统配置管理器:

public class SystemConfigManager {
    private static SystemConfigManager instance;
    private Properties configuration;

    private SystemConfigManager() {
        configuration = new Properties();
        loadConfiguration();
    }

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

    private void loadConfiguration() {
        try (InputStream input = new FileInputStream("config.properties")) {
            configuration.load(input);
        } catch (IOException ex) {
            System.err.println("无法加载配置文件:" + ex.getMessage());
        }
    }

    public String getConfigValue(String key) {
        return configuration.getProperty(key);
    }

    public void setConfigValue(String key, String value) {
        configuration.setProperty(key, value);
        saveConfiguration();
    }

    private void saveConfiguration() {
        try (OutputStream output = new FileOutputStream("config.properties")) {
            configuration.store(output, "系统配置");
        } catch (IOException ex) {
            System.err.println("无法保存配置文件:" + ex.getMessage());
        }
    }
}

2.日志管理

public class Logger {
    private static Logger instance;
    private FileWriter fileWriter;

    private Logger() {
        try {
            fileWriter = new FileWriter("application.log", true);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

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

    public void log(String message) {
        try {
            fileWriter.write(new Date() + ": " + message + "\n");
            fileWriter.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

// 使用示例
public class Application {
    public void performAction() {
        Logger logger = Logger.getInstance();
        logger.log("Action performed successfully");
    }
}

单例模式的典型应用领域

  1. 资源管理:连接池、缓存
  2. 全局状态控制:配置管理、系统设置
  3. 硬件交互:设备管理、外围设备控制
  4. 日志记录:集中式日志管理
  5. 服务协调:线程池、任务调度

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

相关文章:

  • 【Linux相关】服务器无网情况配置conda
  • 011变长子网掩码
  • 嵌入式硬件面试题【经验】总结----会不断添加更新
  • shell第二次作业
  • 人工智能的微积分基础
  • 如何监控Elasticsearch集群状态?
  • ASUS/华硕天选5Pro酷睿版 FX607J 原厂Win11 23H2系统 工厂文件 带ASUS Recovery恢复
  • 使用Apache HttpClient发起一个GET HTTP请求
  • C++(模板)
  • 新增工作台模块,任务中心支持一键重跑,MeterSphere开源持续测试工具v3.5版本发布
  • 线段树讲解
  • 宠物领养技术:SpringBoot框架应用
  • 一个简洁的ajax注册登录找回密码切换的前端页面
  • 原生js上传图片
  • Spring 返回JSON
  • Rust个人认为将抢占C和C++市场,逐渐成为主流的开发语言
  • Hackathon靶机系列Hackathon2
  • 求助:selenium.common.exceptions.SessionNotCreatedException: x x x
  • 【小白学机器学习41】如何从正态分布的总体中去抽样? 获得指定正态分布的样本的2种方法
  • 存储结构及关系(一)
  • 计算机的错误计算(一百六十九)
  • 力扣700:二叉搜索树中的搜索
  • UICollectionView在xcode16编译闪退问题
  • 如何查看ubuntu服务器的ssh服务是否可用
  • 【浏览器】缓存与存储
  • Java WEB:从起源到现代的传奇之旅