单例模式(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. 服务协调:线程池、任务调度
上一篇:Java集成Sa-Token进行认证与授权


下一篇:无核显主机U盘安装Ubuntu和N卡驱动和cuda-toolkit踩坑记录