class Singleton {
private static Singleton instance;
// 构造方法私有化
private Singleton() {
}
// 得到Singleton的唯一途径
pubilc static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
Singleton s1 = Sigleton.getInstance();
Singleton s2 = Sigleton.getInstance();
if (s1 == s2) {
System.out.println("两个对象是相同的实例");
}
应用场景
总的来说,单例模式通常适用于在整个应用程序中只需要一个实例化对象的场景,以确保资源的高效利用和应用程序的稳定性。
需求:对于项目中的 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
类符合单例模式的要求:它保证了在整个应用程序中只有一个实例存在,并提供了全局访问点来获取这个唯一的实例。这样做可以确保配置信息在整个系统中只有一个实例,避免了多次加载配置文件或多次访问数据库的问题。
在饿汉模式下,实例在类加载时就被创建,因此称为“饿汉”——因为它一开始就“吃饱了”。Spring IOC容器中ApplicationContext本身就是典型的饿汉式单例。
特点
适用场景: 当单例对象的创建和初始化操作比较简单,且在程序运行时就需要频繁使用时,可以考虑使用饿汉模式。
代码实现:
public class HungrySingletonTest {
//写法一
private static final HungrySingletonTest hungrySingletonTest = new HungrySingletonTest();
private HungrySingletonTest() {}
public static HungrySingletonTest getInstance() {
return hungrySingletonTest;
}
}
写法二:
public class HungrySingletonTest {
// 写法二
private static HungrySingletonTest hungrySingletonTest;
static {
hungrySingletonTest = new HungrySingletonTest();
}
private HungrySingletonTest() {}
public static HungrySingletonTest getInstance() {
return hungrySingletonTest;
}
}
注意: 饿汉式虽然是线程安全的,但是如果我们使用FileInputStream / FileOutputStream配合ObjectInputStream / ObjectOutputStream来把实例序列化之后,再反序列化回来,还是会变成两个对象。
解决方法:需要在单例类中实现Serializable序列化接口,并且实现readResolve()方法,这是序列化和反序列化的一个协议,为了序列化后的对象能够重复利用,可以实现readResolve()方法,而这个方法是由JVM自动调用的。
private Object readResolve() {
return INSTANCE; // 返回饿汉式中的静态实例
}
懒汉模式:
在懒汉模式下,实例在第一次使用时才进行创建,因此称为“懒汉”——直到需要才“吃”。
特点:
getInstance()
方法时才会创建实例。 适用场景:
代码实现:
public class LazySingletonTest {
private static LazySingletonTest lazySingletonTest;
// 私有构造方法
private LazySingletonTest() {}
public static LazySingletonTest getInstance() {
// 双重锁增加线程安全
// 第一个if()减少synchronized使用频率,提高性能
if (lazySingletonTest == null) {
synchronized (LazySingletonTest.class) {
// 第二个if()去除已经在并发线程中实例化的变量
if (lazySingletonTest == null) {
lazySingletonTest = new LazySingletonTest();
}
}
}
return lazySingletonTest;
}
}
总结
具体如何选择呢,像示例一中的代码,使用懒汉模式(带双重检查锁定)是比较合适的选择。因为:
JsonFormatter
类中的 ObjectMapper
实例需要在第一次调用 getInstance()
方法时才被初始化。这种延迟加载的方式可以节省资源,特别是在应用程序启动时,可能不立即需要操作 JSON 的情况下。 JsonFormatter
实例。这种方式在保证线程安全的同时,又能避免每次调用 getInstance()
都进行同步,提高了性能。ObjectMapper
可能比较重量级(尤其是在配置了特定的序列化/反序列化规则时),懒汉模式可以避免不必要的对象创建和初始化,从而提高了资源的利用率。// 这种形式兼顾饿汉式的内存浪费,也兼顾synchronized性能问题
// 完美地屏蔽了这两个缺点
public class LazySingletonTest {
// 默认使用LazySingleTest的时候,会先初始化内部类
// 如果没使用的话,内部类是不加载的
private LazySingletonTest() {}
// 每一个关键字都不是多余的
// static是为了使单例的空间共享
// 保证这个方法不会被重写,重载
public static final LazySingletonTest getInstance() {
// 在返回结果以前,一定会先加载内部类
return LazyHolder.LAZY;
}
// 默认不加载
private static class LazyHolder {
private static final LazySingletonTest LAZY = new LazySingletonTest();
}
}