【设计模式 01】单例模式

news2024/11/14 22:05:17

单例模式,是一种创建型设计模式,他的核心思想是保证一个类只有一个实例(即,在整个应用程序中,只存在该类的一个实例对象,而不是创建多个相同类型的对象),并提供一个全局访问点来访问这个实例(即,其他类只能通过一个,可以是静态方法,来获取到这个唯一实例)。

优势:因为只有一个实例,既避免了多次创建相同的对象,节省了系统资源。多个类,也就是模块之间可以通过单例实例来共享数据;同时方便我们站在全局的角度控制唯一实例的访问(即,可以严格的控制客户怎样访问它、何时访问他);此外,单例模式还允许只在需要时才进行实例化,即所谓的懒加载,以提高程序的性能。

实现一个单例设计模式需要满足以下的基本要求首先,任何外部代码不能够随意创建实例,也就意味着类的构造函数只能私有;其次,任何外部代码也不能够随意访问实例中的任何资源,也就意味着所有的静态实例变量须是私有的;最后,需要设置一个公有的静态方法,以便外部能够获取到实例的内部资源。

单例模式的实现方式有:懒汉式(只有在遇到请求实例时才会创建一个实例,并且如果已经创建过,就会返回已有的实例)、饿汉式(类加载时就已经完成了实例的创建,不管创建的实例在后面会不会使用,先创建再说)等。

在多线程环境下,由于饿汉式在程序启动阶段就完成实例的初始化,因此不存在多个线程同时尝试初始化实例的问题;但是懒汉式中多个线程同时访问 GetInstance() 方法,并且在同一时刻检测到实例没有被创建(只要线程切换足够频繁就有可能发生),就可能会同时创建实例,从而导致多个实例被创建,(好比你和小明都发现家里每米了,在你们没有相互通知的情况下,都会去超市买一袋米,这样就重复购买了,违背了单例模式)这种情况下我们可以采用一些同步机制,例如使用互斥锁来确保在任何时刻只有一个线程能够执行实例的创建。

以下是单例设计模式的基本写法,以Java代码为例

  • 饿汉式:
public class Singleton {
    private static final Singleton instance = new Singleton(); // 类加载时就被创建

    private Singleton() {
        // 私有构造方法,防止外部实例化
    }

    public static Singleton getInstance() {
        return instance;
    }
} 
  • 懒汉式 + 互斥锁:
public class Singleton {
    private static final Singleton instance;

    private Singleton() {
        // 私有构造方法,防止外部实例化
    }

    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance == new Singleton();
        }
        return instance;
    }
} 
  • 懒汉式 + 双重检查锁提高性能:
public class Singleton {
    private static final Singleton instance;

    private Singleton() {
        // 私有构造方法,防止外部实例化
    }

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

在Java中,关键字synchronized用于实现线程的同步。

首先,synchronized可以用来修饰方法,表示该方法在同一时间只能被一个线程访问。当一个线程访问该方法时,其他线程必须等待该线程执行完毕才能访问该方法。

其次,synchronized还可以用来修饰代码块。当多个线程需要访问共享资源时,可以使用synchronized来保证同一时间只有一个线程可以访问该资源。synchronized代码块使用一个对象作为锁,当一个线程进入synchronized代码块时,会尝试获取该对象的锁,如果锁被其他线程占用,则该线程被阻塞,直到锁被释放。

synchronized的作用是避免多个线程对共享资源的并发访问导致的数据不一致或者错误。通过使用synchronized,可以保证在同一时间只有一个线程对共享资源进行访问,从而保证了线程安全。

"类.class"是Java编程中的一种语法结构,用于获取某个类的Class对象。在Java中,每个类都对应一个Class对象,该对象包含了有关类的结构、字段、方法等信息,可以在程序运行时通过反射机制来访问和操作类的成员。

通过类名后面添加".class",可以获得该类的Class对象。例如,"String.class"返回String类的Class对象,"Integer.class"返回Integer类的Class对象。

使用Class对象可以进行一些操作,比如实例化对象、访问类的静态成员、获取类的父类和接口等。

请注意,类.class只能用于获取该类的Class对象,不能用于实例化这个类的对象。要实例化一个类的对象,可以使用Class对象中的newInstance()方法或者使用构造函数来创建对象。

总结一下,我们应当什么时候使用单例设计模式:

  1. 如果多个模块都需要共享某一种资源,例如一个全局的配置管理器来存储管理配置信息、管理数据库连接池信息等,可以使用单例设计模式。
  2. 如果创建对象本身就比较消耗资源,而且可能在整个程序中都不一定会使用,就可以使用单例模式中的懒加载;
  3. 有些场景中只需要一个实例就足以协调所有行为,创建多个实例没有必要甚至会导致不好的后果,例如管理应用程序中的缓存、管理线程池。

管理应用程序中的缓存只需要一个缓存实例的原因是为了保持数据的一致性和避免冲突。

当应用程序使用多个缓存实例时,可能会导致以下问题:

  1. 数据一致性:如果多个缓存实例各自独立管理数据,那么在不同实例中的数据可能会不一致。当更新或删除数据时,如果没有及时同步所有的缓存实例,可能导致数据的不一致性,从而引发潜在的问题。

  2. 资源浪费:每个缓存实例都需要占用内存和其他资源,如果使用多个缓存实例,会造成资源的浪费。而只使用一个缓存实例可以更有效地利用资源。

  3. 缓存冲突:多个缓存实例可能会导致相同的数据被同时缓存在不同的实例中,从而造成缓存冲突。如果多个实例同时读写相同的数据,可能会引发并发问题,影响应用程序的性能和正确性。

通过只使用一个缓存实例,可以确保数据的一致性,并最大程度地节省资源。可以使用单例模式来创建和管理缓存实例,确保应用程序中只存在一个缓存对象。这样可以简化缓存管理的操作,提高系统的可靠性和性能。

【设计模式专题之单例模式】1.小明的购物车

 CPP版本:

#include <iostream>
#include <vector>
#include <map>
 
using namespace std;
 
class ShoppingCartManager {
public:
    // 获取购物车实例
    static ShoppingCartManager& getInstance() {
        static ShoppingCartManager instance;
        return instance;
    }
 
    // 添加商品到购物车
    void addToCart(const string& itemName, int quantity) {
        if (cart.find(itemName) == cart.end()) {
            order.push_back(itemName);
        }
        cart[itemName] += quantity;
    }
 
    // 查看购物车
    void viewCart() const {
        for (const auto& itemName : order) {
            cout << itemName << " " << cart.at(itemName) << endl; 
            // 不能使用cart[itemName],因为处在一个const方法,不能有修改变量值的格式出现。
        }
    }

private:
    // 私有构造函数
    ShoppingCartManager() {}
 
    // 购物车存储商品和数量的映射
    map<string, int> cart;
    // 记录加入购物车的顺序
    vector<string> order;
};

int main() {
    string itemName;
    int quantity;
 
    while (cin >> itemName >> quantity) {
        // 获取购物车实例并添加商品
        ShoppingCartManager& cart = ShoppingCartManager::getInstance();
        cart.addToCart(itemName, quantity);
    }
 
    // 输出购物车内容
    const ShoppingCartManager& cart = ShoppingCartManager::getInstance();
    cart.viewCart();
 
    return 0;
}

在C++中,static关键字可以有多种作用

  1. 静态变量(Static Variables):在函数内使用static关键字,可以创建静态变量。静态变量在函数调用结束后仍然存在,并且在下一次调用函数时保持其值不变。这使得静态变量在需要记住上一次函数调用结果或在多次调用之间共享数据时非常有用。

  2. 静态成员变量(Static Member Variables):在类内部使用static关键字,可以创建静态成员变量。静态成员变量属于类本身而不是类的实例,因此在不创建类的对象时也可以访问和修改它们。静态成员变量在类的所有实例之间共享数据。

  3. 静态函数(Static Functions):在类内部使用static关键字,可以创建静态函数。静态函数不依赖于类的实例,因此可以直接通过类名调用,无需创建类的对象。静态函数主要用于处理和类本身相关的操作,而不是与类的实例数据交互。

  4. 静态类(Static Classes):在C++中,可以将类声明为static类。静态类的成员函数和成员变量都必须是静态的,而且无法创建静态类的对象。静态类主要用于实现一些全局可访问的、不需要创建对象的功能,类似于命名空间的作用。

总的来说,C++中的static关键字可以用于创建静态变量、静态成员变量、静态函数以及静态类,它们都具有特定的作用和用途。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1486856.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

机器学习|KNN和Kmeans

KNN和Kmeans KNN KNN-K个最近的邻居&#xff0c;而K是可人先预设出来的。 所谓近朱者赤&#xff0c;近墨者黑。 可以选取离当前最近的K个样本来作为辅助判断&#xff0c;因为本样本和最近的K个样本应该是处于一种相似的状态。 以下是一个苹果和梨的识别任务。 图上会出现一个未…

JavaScript进阶-高阶技巧

文章目录 高阶技巧深浅拷贝浅拷贝深拷贝 异常处理throw抛异常try/caych捕获异常debugger 处理thisthis指向改变this 性能优化防抖节流 高阶技巧 深浅拷贝 只针对引用类型 浅拷贝 拷贝对象后&#xff0c;里面的属性值是简单数据类型直接拷贝值&#xff0c;如果属性值是引用数…

STM32(8)NVIC编程

中断源由部分片上外设产生 在misc.h中找&#xff0c;杂项 配置NVIC GPIO和AFIO不能产生中断源&#xff0c;但能通过EXTI&#xff0c;由EXTI产生中断源 NVIC不需要开启时钟&#xff0c;因为NVIC模块位于内核内部&#xff0c;芯片一上电就能工作。 中断响应函数 中断向量表在启…

移动感知终端软件发布过程中遇到的问题以及解决方案

一.软件发布 软件的效果展示在文章&#xff1a; 网络图谱构建系统目前已实现的功能-CSDN博客 在android studio编写完程序之后&#xff0c;要打包并发布软件&#xff0c;供其他用户使用&#xff0c;以下几篇文章给出了方法&#xff1a; Android Studio使用签名打包发布APP&…

day09_商品管理订单管理SpringTaskEcharts

文章目录 1 商品管理1.1 添加功能1.1.1 需求说明1.1.2 核心概念SPUSKU 1.1.3 加载品牌数据CategoryBrandControllerCategoryBrandServiceCategoryBrandMapperCategoryBrandMapper.xml 1.1.4 加载商品单元数据ProductUnitProductUnitControllerProductUnitServiceProductUnitMap…

Qt 简约美观的动画 摆钟风格 第十季

&#x1f60a; 今天给大家分享一个摆钟风格的加载动画 &#x1f60a; 效果如下: 最近工作忙起来了 , 后续再分享其他有趣的加载动画吧. 一共三个文件 , 可以直接编译运行 //main.cpp #include "LoadingAnimWidget.h" #include <QApplication> #include <Q…

构建安全的REST API:OAuth2和JWT实践

引言 大家好&#xff0c;我是小黑&#xff0c;小黑在这里跟咱们聊聊&#xff0c;为什么REST API这么重要&#xff0c;同时&#xff0c;为何OAuth2和JWT在构建安全的REST API中扮演着不可或缺的角色。 想象一下&#xff0c;咱们每天都在使用的社交媒体、在线购物、银行服务等等…

Spring框架精髓:带你手写IoC

个人名片&#xff1a; &#x1f43c;作者简介&#xff1a;一名大三在校生&#xff0c;喜欢AI编程&#x1f38b; &#x1f43b;‍❄️个人主页&#x1f947;&#xff1a;落798. &#x1f43c;个人WeChat&#xff1a;hmmwx53 &#x1f54a;️系列专栏&#xff1a;&#x1f5bc;️…

C语言之OJ刷题

今天刷一下题 刷的不多 第一道 链表的回文结构 仔细看这个题它是有限制条件的 首先是时间复杂度和空间复杂度 所以我们并不是用数组去做 但怎么做呢&#xff1f; 思路 既然是判断是否是回文结构&#xff0c;那么我们就找一下他的中间节点 然后将后半段倒置 进行比较…

【简说八股】Redisson的守护线程是怎么实现的

Redisson Redisson 是一个 Java 语言实现的 Redis SDK 客户端&#xff0c;在使用分布式锁时&#xff0c;它就采用了「自动续期」的方案来避免锁过期&#xff0c;这个守护线程我们一般也把它叫做「看门狗」线程。 Redission是一个在Java环境中使用的开源的分布式缓存和分布式锁实…

C2远控Loader红队技巧

inlineHook技术(钩子技术) MessageBoxA C自带弹窗函数 test_MessageBoxA 代码中自定义函数 InlineHook技术&#xff1a;testA原本插入jmp指令跳转到testB&#xff0c;实现testB自定义的函数 实现方式&#xff1a;X86&#xff1a; // 方式一&#xff0c;使用jmp相对地址跳转…

基于springboot音乐翻唱与分享平台源码和论文

1.1研究背景 随着网络不断的普及发展&#xff0c;音乐网站与分享平台依靠网络技术的支持得到了快速的发展&#xff0c;首先要从用户的实际需求出发&#xff0c;通过了解用户的需求开发出具有针对性的首页、音乐资讯、音乐翻唱、在线听歌、留言反馈、个人中心、后台管理、客服功…

ABAP - SALV教程16 合计、小计

虽然ALV的标准状态栏功能就能实现合计、小计、平均值、最大值等这些功能&#xff0c;但用户更希望一进去ALV就希望ALV已经对数量&#xff0c;金额的字段进行合计&#xff0c;小计。SALV实现合计&#xff0c;调用CL_SALV_AGGREGATIONS的ADD_AGGREGATION即可 DATA(lo_aggrs) …

[数据结构]链表OJ--环形链表判断是否有环(快慢指针法)

141. 环形链表 - 力扣&#xff08;LeetCode&#xff09; 这里我采用的是快慢指针法,这是我认为最容易理解的方法,这个方法的思路是这样的. 我们可以定义两个指针一个快一个慢,如果这个链表有环,则快慢指针一定会相遇. 这里我画图举个例子: 我们很明显的可以看出,有环链表,快指…

成功解决git clone遇到的error: RPC failed; curl 16 Error in the HTTP2 framing layer fatal: expected flush af

成功解决git clone遇到的error: RPC failed; curl 16 Error in the HTTP2 framing layer fatal: expected flush af 问题描述解决方案 问题描述 用git的时候可能会遇到这个问题&#xff1a; (base) zhouzikang7443-8x4090-120:~/project$ git clone https://github.com/123/12…

Outlook邮箱IMAP密码怎么填写?账户设置?

Outlook邮箱IMAP密码是什么&#xff1f;Outlook如何设置IMAP&#xff1f; 许多用户会选择通过IMAP协议将邮箱与各种邮件客户端进行连接。而在设置过程中&#xff0c;填写IMAP密码是必不可少的一步。那么&#xff0c;Outlook邮箱的IMAP密码应该如何填写呢&#xff1f;接下来&am…

Matlab 最小二乘插值(曲线拟合)

文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 在多项式插值时,当数据点个数较多时,插值会导致多项式曲线阶数过高,带来不稳定因素。因此我们可以通过固定幂基函数的最高次数 m(m < n),来对我们要拟合的曲线进行降阶。之前的函数形式就可以变为: 二、实现…

【硬件工程师面经整理13_电容电阻电感等效电路】

1 电容/电阻/电感的等效电路 ①电容的等效电路是由一个电容和一个电阻组成的&#xff0c;其中电阻称为ESR&#xff08;Equivalent Series Resistance&#xff0c;等效串联电阻&#xff09;。在真实情况下&#xff0c;一个电容会被表示成由“一个电容一个电阻一个电感”组合而成…

冒泡排序 和 qsort排序

目录 冒泡排序 冒泡排序部分 输出函数部分 主函数部分 总代码 控制台输出显示 总代码解释 冒泡排序优化 冒泡排序 主函数 总代码 代码优化解释 qsort 排序 qsort 的介绍 使用qsort排序整型数据 使用qsort排序结构数据 冒泡排序 首先&#xff0c;我先介绍我的冒泡…

解决虚拟机启动报错:“End kernel panic - not syncing: attempted to kill the idle task”

原本能正常运行的虚拟机&#xff0c;很长一段时间没用后&#xff0c;今天再次启动&#xff0c;然后就出现下面的问题&#xff1a; 然后走了一些弯路&#xff0c;比如说删除该虚拟机然后新建一个虚拟机&#xff08;问题未解决&#xff09;、直接删除VitualBox重新安装&#xff0…