首页 > 其他分享 >设计模式(单例模式)

设计模式(单例模式)

时间:2024-08-15 16:26:35浏览次数:15  
标签:模式 instance 实例 static private 单例 设计模式 public

概念

  • 单例模式(Singleton Pattern)是指确保一个类在一个容器下只有一个实例存在,并提供一个全局访问点。单例模式最大的特点就是构造方法私有化。
  • 通常情况下,可以让一个全局变量使得一个对象被访问,但它不能防止你实例化多个对象。一个最好的方法就是让类自身负责保存它的唯一实例。这个类可以保证没有其他实例可以被创建,并且它可以提供一个访问实例的方法。
  • Singleton类,定义一个getInstabce操作,允许客户访问它的唯一实例。getInstance是一个静态方法,主要负责创建自己的唯一实例。
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("两个对象是相同的实例");
}

应用场景 

  • 日志系统:在应用程序中,通常只需要一个日志系统,通过单例模式可以避免在多个地方创建多个日志对象,降低资源消耗。‌
  • 数据库连接池数据库连接池是一个重要的资源,使用单例模式可以确保应用程序中只有一个数据库连接池实例,从而避免资源浪费。
  • 配置文件管理器:为了管理应用程序的配置文件,通常只需要一个配置文件管理器实例。单例模式可以确保整个应用程序中只有一个配置文件管理器实例。
  • 缓存系统:缓存系统是提高应用程序性能的关键组件,使用单例模式可以确保整个应用程序中只有一个缓存实例。
  • GUI组件:在图形用户界面(GUI)开发中,单例模式可以确保整个应用程序中只有一个GUI组件实例,以保持用户界面的一致性和稳定性。
  • 读取配置信息:如果配置信息需要在程序启动时加载,并且只需读取一次,那么可以使用单例模式来读取配置文件。‌

总的来说,单例模式通常适用于在整个应用程序中只需要一个实例化对象的场景,以确保资源的高效利用和应用程序的稳定性。 

 示例1

需求:对于项目中的 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 数据的项目中,可以显著提升性能和资源利用率。

示例2 

 需求:通过单例模式可以确保配置信息在整个系统中只有一个实例,避免多次加载配置文件或多次访问数据库。

创建配置信息类:这个类负责加载和存储配置信息

// 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();
    }
}

标签:模式,instance,实例,static,private,单例,设计模式,public
From: https://blog.csdn.net/aplis/article/details/141206613

相关文章

  • 面试官:JDK中都用了哪些设计模式?
    设计模式是前辈们经过实践验证总结的解决方案,帮助我们构建出更具可维护性、可扩展性和可读性的代码。当然,在面试的过程中,也会或多或少的被问到。那么今天,我们就来看一道设计模式中的常见面试问题:JDK中都用了哪些设计模式?我按照大家比较熟悉且好理解的方式,把JDK中使用的设计模......
  • 解决 Docker CE 在无根模式(rootless)下无法通过 IPv6 拉取映像的问题
    折腾一天快把我逼疯了本来Docker对IPv6的支持就不好,再来个rootless,雪上加霜首先,我们要区分DockerEngine和里面的Image。拉取映像是DockerEngine在工作,也就是那个Daemon本身,而不是某个container或image。RootlessDocker使用RootlessKit来管理用户命名......
  • 超低功耗模式在合宙模组中的应用与配置
    随着物联网技术的飞速发展,设备功耗成为影响系统稳定性和续航能力的重要因素。合宙科技推出的多款模组,如Air780E&600E(EC618平台)、780EP系列(EC718平台)、以及780EL_780ET_700EL_700ET系列(EC716S平台),均支持超低功耗模式,以满足不同应用场景下的节能需求。本文将详细介绍两种主要的低......
  • 设计模式的7大基本原则
    设计模式是解决问题的经验总结,是软件开发中常用的一种设计思想和方法。在软件开发过程中,遵循设计模式可以提高代码的可重用性、可维护性和可扩展性。设计模式的基本原则是软件开发过程中的指导方针,它们是在解决问题时需要遵循的基本原则。本文将介绍设计模式的7大基本原则,......
  • VMware中的三种网络模式
    VMware中的三种网络模式介绍VMware提供了三种网络工作模式,分别是Bridged(桥接模式)、NAT(网络地址转换模式)*和*Host-Only(仅主机模式)。当VMware软件安装完成之后,会在电脑上虚拟出三块虚拟交换机和两块虚拟网卡。VMnet0:用于虚拟桥接网络下的虚拟交换机。VMnet1:用于虚拟Host-Onl......
  • 增量生成器简化BlazorServer兼容BlazorAuto模式
    本文简略介绍一下如何使用增量生成器(IncrementalGenerator)简化BlazorServer兼容Auto模式比如现在有一个BlazorServer项目的Razor页面//UserIndex.razor@code{[Inject,NotNull]IUserService?Service{get;set;}}如果IUserService的实现不支持运行在WebAssemb......
  • ESP8266 AP模式配置WIFI
    #include<Arduino.h>#include<ESP8266WiFi.h>#include<WiFiManager.h>//导入WIFI管理模块voidsetup(){Serial.begin(115200);//配置示波器默认频率WiFiManagermanager;//实例化WIFI管理对象manager.autoConnect("esp8266","12345678&q......
  • 设计模式-延迟加载(Lazy Load)
    概念一个对象,它虽然不包含所需要的所有数据,但是知道怎么获取这些数据。加载一个对象会引起大量相关对象的加载,这样会损害系统的性能。延迟加载会暂时终止这个加载过程。运行机制四种实现延迟加载的方法:延迟初始化(Lazyinitialization)。每次访问属性域都要先检查该域是否......
  • 跨越传统模式:高性能文件采集系统如何改变数据管理?
    高性能文件采集通常指的是使用高效的技术和工具来收集、存储和管理大量的文件数据。这在需要处理大规模数据集的领域尤为重要,比如人工智能、大数据分析、内容管理系统等。 人工智能行业,高性能文件采集会涉及到以下类型文件:1.文本文件:包括学术论文、技术文档、专利、新闻报道......
  • Java中的代理模式(个人学习笔记)
    什么是代理代理是一种设计模式,提供了对目标对象另外的访问方式。(用户不需要直接访问目标对象,只需要接触代理对象就能实现访问)代理的好处目标对象可以被间接访问可以在目标对象实现的基础上实现额外的功能(除了目标对象提供的方法外,代理可以额外提供一些实用的方法),即扩展......