目录
一、定义
二、应用场景
单例模式的应用场景主要包括以下几个方面:
日志系统:在应用程序中,通常只需要一个日志系统,通过单例模式可以避免在多个地方创建多个日志对象,降低资源消耗。
数据库连接池:数据库连接池是一个重要的资源,使用单例模式可以确保应用程序中只有一个数据库连接池实例,从而避免资源浪费。
配置文件管理器:为了管理应用程序的配置文件,通常只需要一个配置文件管理器实例。单例模式可以确保整个应用程序中只有一个配置文件管理器实例。
缓存系统:缓存系统是提高应用程序性能的关键组件,使用单例模式可以确保整个应用程序中只有一个缓存实例。
GUI组件:在图形用户界面(GUI)开发中,单例模式可以确保整个应用程序中只有一个GUI组件实例,以保持用户界面的一致性和稳定性。
读取配置信息:如果配置信息需要在程序启动时加载,并且只需读取一次,那么可以使用单例模式来读取配置文件。
此外,单例模式还适用于那些创建对象时资源消耗大,但又需要频繁访问该对象的场景,或者需要对系统内的资源进行统一的读写操作的场景,比如进行配置文件的读写操作。
总的来说,单例模式通常适用于在整个应用程序中只需要一个实例化对象的场景,以确保资源的高效利用和应用程序的稳定性。
三、具体实现
示例一
“ 需求:对于项目中的 JSON 要格式化处理对象,采用 双检锁单例模式 进行管理,从而复用对象,避免重复创建对象的开销 ”
实现:
创建单例:
import com.fasterxml.jackson.databind.ObjectMapper; // JsonFormatter 类负责管理 ObjectMapper 对象,它是 Jackson 库中用于处理 JSON 的核心类。 public class JsonFormatter { private static volatile JsonFormatter instance; private ObjectMapper objectMapper; // 私有构造方法,防止外部实例化 private JsonFormatter() { // 初始化 ObjectMapper objectMapper = new ObjectMapper(); // 可以在这里配置 ObjectMapper 的特性,例如日期格式化、空字段处理等 } // 获取单例实例的静态方法 // 使用双检锁(double-checked locking)来确保在多线程环境下只创建一个 JsonFormatter 实例。 public static JsonFormatter getInstance() { if (instance == null) { //volatile 关键字确保在多线程环境中正确地处理 instance 变量,防止指令重排序带来的问题。 synchronized (JsonFormatter.class) { if (instance == null) { instance = new JsonFormatter(); } } } return instance; } // 提供了一个公共方法来格式化 JSON 字符串,可以将对象转换为 JSON 格式的字符串。 public String formatJson(Object obj) { try { return objectMapper.writeValueAsString(obj); } catch (Exception e) { e.printStackTrace(); return null; } } }
使用单例:
public class MyApp { public static void main(String[] args) { JsonFormatter jsonFormatter = JsonFormatter.getInstance(); // 示例对象 MyObject obj = new MyObject("John Doe", 30); // 格式化为 JSON 字符串 String jsonString = jsonFormatter.formatJson(obj); System.out.println("Formatted JSON: " + jsonString); } }
使用双检锁单例模式来管理 JSON 格式化处理对象,确保在整个项目中只有一个 JsonFormatter
实例存在,避免了重复创建对象的开销,同时提供了一个便捷的方式来操作 JSON 数据的格式化。这种方式非常适合在需要频繁处理 JSON 数据的项目中,可以显著提升性能和资源利用率。
示例二
“需求:通过单例模式可以确保配置信息在整个系统中只有一个实例,避免多次加载配置文件或多次访问数据库”
实现:
创建配置信息类:这个类负责加载和存储配置信息
// ConfigManager.java - 单例模式的配置管理器 // ConfigManager 类可以进一步扩展,例如支持从文件中加载配置、支持动态更新配置、支持不同环境的配置切换等。这样,通过单例模式管理配置信息,能够有效地避免多次加载配置文件或访问数据库,提高系统性能和管理便捷性。 public class ConfigManager { private static ConfigManager instance; //静态单例实例变量,使用 private static 关键字声明了一个静态的 instance 变量,用于保存 ConfigManager 类的唯一实例。 private String configFile; // 用于存储配置文件名或配置内容 // 私有构造方法,防止外部实例化 private ConfigManager() { // 加载配置文件或初始化配置内容 this.configFile = "config.properties"; // 示例配置文件名 // 实际应用中可以在构造方法中进行配置文件的加载 // 例如:this.loadConfig(); } // 公有静态方法,获取唯一实例,getInstance() 方法是获取 ConfigManager 类的实例的唯一入口。这个方法使用了双重检查锁定(double-checked locking)来确保在多线程环境下也能保持单例的唯一性和线程安全性。 public static ConfigManager getInstance() { if (instance == null) { synchronized (ConfigManager.class) { if (instance == null) { instance = new ConfigManager(); } } } return instance; } // 示例方法:获取配置信息 public String getConfig() { return this.configFile; } // 示例方法:设置配置信息 public void setConfig(String configFile) { this.configFile = configFile; } }
实现单例模式:确保在整个应用中只有一个配置信息实例。
public class MyApp { public static void main(String[] args) { ConfigManager configManager = ConfigManager.getInstance(); // 获取配置信息示例 String configFile = configManager.getConfig(); System.out.println("Current config file: " + configFile); // 修改配置信息示例 configManager.setConfig("new_config.properties"); System.out.println("Updated config file: " + configManager.getConfig()); } }
整个 ConfigManager
类符合单例模式的要求:它保证了在整个应用程序中只有一个实例存在,并提供了全局访问点来获取这个唯一的实例。这样做可以确保配置信息在整个系统中只有一个实例,避免了多次加载配置文件或多次访问数据库的问题。
四、懒汉与饿汉
在选择单例模式的创建方式时,通常可以选择懒汉模式或饿汉模式,具体取决于项目的需求和使用场景。让我们来比较一下懒汉模式和饿汉模式的特点和适用场景:
饿汉模式
在饿汉模式下,实例在类加载时就被创建,因此称为“饿汉”——因为它一开始就“吃饱了”。
特点:
- 线程安全:由于实例在类加载时就创建并初始化,所以不存在多线程环境下的线程安全问题。
- 简单:实现起来比较简单,没有复杂的同步控制。
- 性能较好:在访问量较大或者对性能有一定要求的场景下,由于不需要在获取实例时进行同步操作,性能较好。
适用场景:
- 当单例对象的创建和初始化操作比较简单,且在程序运行时就需要频繁使用时,可以考虑使用饿汉模式。
public class Singleton { private static final Singleton instance = new Singleton(); private Singleton() { // 私有构造方法 } public static Singleton getInstance() { return instance; } }
懒汉模式
在懒汉模式下,实例在第一次使用时才进行创建,因此称为“懒汉”——直到需要才“吃”。
特点:
- 延迟加载:只有在首次调用
getInstance()
方法时才会创建实例。 - 线程安全性需要考虑:如果不加同步控制,在多线程环境下可能会创建多个实例。
- 资源利用率高:只有在需要时才会创建对象,节省了资源。
适用场景:
- 当单例对象的创建和初始化操作较为复杂或者需要延迟加载时,可以考虑使用懒汉模式。
- 如果应用中频繁使用单例对象的情况不多,懒汉模式可以节省资源。
示例(带双重检查锁定的懒汉模式):
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; } }
五、总结
- 饿汉模式适合在单例对象比较简单,且在程序整个生命周期内需要频繁使用的情况下,可以提升性能。
- 懒汉模式适合在单例对象创建和初始化较为复杂,或者需要延迟加载的情况下,以节省资源并避免不必要的初始化。
具体如何选择呢,像示例一中的代码,使用懒汉模式(带双重检查锁定)是比较合适的选择。因为:
延迟加载:你的
JsonFormatter
类中的ObjectMapper
实例需要在第一次调用getInstance()
方法时才被初始化。这种延迟加载的方式可以节省资源,特别是在应用程序启动时,可能不立即需要操作 JSON 的情况下。线程安全性:通过双重检查锁定,确保了在多线程环境下只会创建一个
JsonFormatter
实例。这种方式在保证线程安全的同时,又能避免每次调用getInstance()
都进行同步,提高了性能。资源利用:由于
ObjectMapper
可能比较重量级(尤其是在配置了特定的序列化/反序列化规则时),懒汉模式可以避免不必要的对象创建和初始化,从而提高了资源的利用率。
六、说明
日志管理上使用@Slf4j 中的log 这个注解里面没有用到单例模式