单例模式总结

总结自:https://juejin.cn/post/6844903928497176584

1、什么是单例模式

单例模式是指在整个系统生命周期内,保证一个类只能产生一个实例,确保该类的唯一性。

2、为什么需要单例模式

单例模式是为了保证程序的线程安全

3、什么是线程安全?

在拥有共享数据多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据污染等意外情况。

4、如何保证线程安全?

  • 给共享的资源加把,保证每个资源变量每时每刻至多被一个线程占用。
  • 让线程也拥有资源,不用去共享进程中的资源。如:使用threadlocal可以为每个线程维护一个私有的本地变量。
  • Go 的 channel 也是一种有效的线程同步机制。

5、单例模式分类

单例模式可以分为 懒汉式 和 饿汉式 ,两者之间的区别在于创建实例的时间不同。

  • 懒汉式:系统运行中,实例并不存在,只有当需要使用该实例时,才会去创建并使用实例。这种方式要考虑线程安全
  • 饿汉式:系统一运行,就初始化创建实例,当需要时,直接调用即可。这种方式本身就线程安全,没有多线程的线程安全问题。

6、单例类的特点

  • 构造函数和析构函数private类型,目的是禁止外部构造和析构。
  • 拷贝构造函数和赋值构造函数private类型,目的是禁止外部拷贝和赋值,确保实例的唯一性。
  • 类中有一个获取实例的静态方法,可以全局访问。

7、单例模式实现

懒汉式:

  • 加锁的懒汉式单例
// 实例在SingleTon类的外部,需要指针
class SingleTon {
public:
    // 获取单实例对象
    static SingleTon* getInstance();

    // 释放单实例,进程退出时调用
    static void deleteInstance();

    // 打印实例地址
    void print();
private:
    // 禁止外部构造
    SingleTon();

    // 禁止外部析构
    ~SingleTon();

    // 禁止外部复制构造
    SingleTon(const SingleTon& signal);

    // 禁止外部赋值操作
    const SingleTon &operator=(const SingleTon& signal);
private:
    // 唯一单实例对象指针
    static SingleTon* m_SingleTon;
    // 唯一互斥锁
    static std::mutex m_Mutex;
};

// 初始化静态成员变量
// 在 C++ 中,非const的静态成员变量必须在类外进行初始化,否则会导致链接错误。
SingleTon* SingleTon::m_SingleTon = nullptr;
std::mutex SingleTon::m_Mutex;

// 注意:不能返回指针的引用,否则存在外部被修改的风险!
SingleTon* SingleTon::getInstance() {
    //  这里使用了两个 if 判断语句的技术称为双检锁;好处是,只有判断指针为空的时候才加锁,
    //  避免每次调用 GetInstance的方法都加锁,锁的开销毕竟还是有点大的。
    if (m_SingleTon == nullptr) {
        std::unique_lock<std::mutex> lock(m_Mutex);
        if (m_SingleTon == nullptr) {
            auto temp = new (std::nothrow)SingleTon();
            m_SingleTon = temp;
        }
    }

    return m_SingleTon;
}

void SingleTon::deleteInstance() {
    if (m_SingleTon) {
        // 删除操作也需要加锁
        std::unique_lock<std::mutex> lock(m_Mutex);
        delete m_SingleTon;
        m_SingleTon = nullptr;
    }
}

void SingleTon::print() {
    std::cout << "我的实例内存地址是:" << this << std::endl;
}

SingleTon::SingleTon() {
    std::cout << "构造函数" << std::endl;
}

SingleTon::~SingleTon() {
    std::cout << "析构函数" << std::endl;
}
  • Go 使用双检锁实现
package singleTon

import (
  "fmt"
  "sync"
)

type SingleTon struct {}

var (
  singleTon *SingleTon
  mutex sync.Mutex
)

func GetInstance() *SingleTon {
  if singleTon == nil {
	mutex.Lock()
	defer mutex.Unlock()
	if singleTon == nil {
	  singleTon = &SingleTon{}
	}
  }
  return singleTon
}
  • 内部静态变量的懒汉单例(C++11线程安全)
// 实例在getInstance函数内部,不需要指针
class SingleTon {
public:
    // 获取单实例对象
    static SingleTon& getInstance();

    // 打印实例地址
    void print();
private:
    // 禁止外部构造
    SingleTon();

    // 禁止外部析构
    ~SingleTon();

    // 禁止外部复制构造
    SingleTon(const SingleTon& signal);

    // 禁止外部赋值操作
    const SingleTon &operator=(const SingleTon& signal);
};

    /*
        利用局部静态的特性实现单实例:
        静态局部变量只在当前函数内有效,其他函数无法访问,相当于加锁操作。
        静态局部变量只在第一次被调用的时候初始化,也存储在数据段,生命周期从第一次被初始化起至程序结束止。
     */
SingleTon& SingleTon::getInstance() {
    static SingleTon signal;
    return signal;
}

void SingleTon::print() {
    std::cout << "我的实例内存地址是:" << this << std::endl;
}

SingleTon::SingleTon() {
    std::cout << "构造函数" << std::endl;
}

SingleTon::~SingleTon() {
    std::cout << "析构函数" << std::endl;
}

/*  
	问题:多线程同时调用 GetInstance() 方法有可能还是会产生竞争。
	解决办法:在程序的单线程启动阶段就调用 GetInstance() 方法。
*/
  • Go 语言 sync.Once 实现
package singleTon

import (
  "fmt"
  "sync"
)

type SingleTon struct {}

var (
  singleTon *SingleTon
  once sync.Once
)

func GetInstance() *SingleTon {
  if singleTon == nil {
	once.Do(func() {
	  singleTon = &SingleTon{}
	})
  }
  return singleTon
}

饿汉式:

  • 饿汉式单例(本身线程安全)C ++ 语言实现
// 实例在SingleTon类的外部,需要指针
class SingleTon {
public:
    // 获取单实例
    static SingleTon getInstance();

    // 释放单实例,进程退出时调用
    static void deleteInstance();

    // 打印实例地址
    void print();

private:
    // 禁止外部构造
    SingleTon();

    // 禁止外部析构
    ~SingleTon();

    // 禁止外部复制构造
    SingleTon(const SingleTon& signal);

    // 禁止外部赋值操作
    const SingleTon &operator=(const SingleTon& signal);

private:
    // 唯一单实例对象指针
    static SingleTon* m_SingleTon;
};

// 代码一运行就初始化创建实例,本身就线程安全
SingleTon* SingleTon::m_SingleTon = new (std::nothrow)SingleTon();

SingleTon SingleTon::getInstance() {
    return m_SingleTon;
}

void SingleTon::deleteInstance() {
    // 如果存在单例,则删除单例在内存中的数据,并将指针置空
    if (m_SingleTon) {
        delete m_SingleTon;
        m_SingleTon = nullptr;
    }
}

void SingleTon::print() {
    std::cout << "我的实例内存地址是:" << this << std::endl;
}

SingleTon::SingleTon() {
    std::cout << "构造函数" << std::endl;
}

SingleTon::~SingleTon() {
    std::cout << "析构函数" << std::endl;
}
  • Go 语言实现
package singleTon

import (
  "fmt"
  "sync"
)

type SingleTon struct {}

singleTon := &SingleTon{}

func GetInstance() *SingleTon {
  return singleTon
}

全部评论

相关推荐

理想汽车前瞻硬件研发面经分享面试背景bg双非一本&nbsp;有两段大厂实习经历干的基本是测试+研发一面技术面(1.5h)全程无八股,聚焦项目细节深挖,节奏紧凑但交流顺畅。1.&nbsp;自我介绍(重点突出半导体+电子技术双背景、竞赛获奖及核心项目经历);2.&nbsp;实习工艺优化的核心难点是什么?测试方案3.项目改进点4.&nbsp;本科期间参与的项目和竞赛中,你觉得自己收获最大的能力是什么?5.&nbsp;项目中遇到过哪些实验数据与预期不符的情况?怎么解决的?6.半导体器件可靠性测试中&nbsp;是如何进行故障定位和参数优化的7.&nbsp;为什么选择自主搭建硬件平台而非用现成开发板?8.场景题输出调控报告9.手撕代码10.为什么要干硬件而不是嵌入式软件反问环节1.&nbsp;若入职,会具体负责哪类产品的研发2.&nbsp;工作中是否有机会深度参与核心参数优化、故障归因这类一线研发工作?压力强度如何?3.团队在器件选型、原理图设计方面有哪些规范或经验沉淀?涵盖产品需求分析、可行性报告撰写、硬件系统方案设计、EMC认证、功能安全设计、器件选型、原理图设计、工艺优化及单板调试,有专门团队负责,与我的技能匹配度很高。后续流程需等待HR面安排。二面HR面(40min)1.&nbsp;自我介绍(突出协作能力和项目成果);2.&nbsp;分享一下实习时,最具挑战性的任务是什么?怎么完成的?3.&nbsp;未来3-5年的职业规划是什么?希望在硬件领域往哪个细分方向深耕?4.并没有那么匹配的研发经历如何胜任工作5.&nbsp;竞赛的心路历程6.&nbsp;你是如何在项目中提升团队凝聚力的?举一个具体例子。7.&nbsp;到岗日期及实习时间反问环节1.公司针对实习生有哪些职业发展路径?是否有技术晋升或跨项目学习的机会?2.公司规模及构成已offer
查看20道真题和解析
点赞 评论 收藏
分享
2025-11-25 15:37
门头沟学院 Java
自我介绍+项目拷打八股:JDK动态代理和CGLib代理的区别是什么?两种代理的优缺点是什么?两种代理的应用场景是什么?高并发场景下,如何安全地更新一个计数器?悲观锁有哪些具体实现的类?乐观锁有哪些具体实现的类?AtomicInteger&nbsp;是什么?(不会)synchronized、Atomic、Lock&nbsp;它们的实现方式具体有什么区别?synchronized&nbsp;有什么优缺点?synchronized&nbsp;一般在哪些场景下比较适用?Java&nbsp;里面内存泄漏和内存溢出这两个概念有什么区别?内存泄漏有哪些典型的例子?一般用什么工具去检测出内存泄漏的场景?如何开启内存泄漏检测?通过什么命令或者说是什么工具?实现深拷贝要怎么做?Java&nbsp;里面创建线程的方式有哪一些?各种创建线程方式的优缺点是什么?线程池的工作原理是什么?描述一下提交任务时,线程池是怎么决定使用核心线程、阻塞队列还是非核心线程的?Spring&nbsp;Boot&nbsp;的自动配置机制是怎么实现的?@Conditional&nbsp;注解是如何确保配置是按需加载的?什么是&nbsp;SQL&nbsp;注入?怎么去避免&nbsp;SQL&nbsp;注入?查看&nbsp;CPU&nbsp;的使用情况用什么&nbsp;Linux&nbsp;命令?查看内存的使用情况用什么&nbsp;Linux&nbsp;命令?查看日志一般用什么&nbsp;Linux&nbsp;命令?实时查看日志的话,tail&nbsp;命令后面要加什么参数?查看日志最后&nbsp;500&nbsp;行的命令怎么写?查看某个关键字对应的日志用什么命令?设计模式用的多吗?用了哪些?策略模式和工厂模式是怎么实现的?具体应用场景是什么?什么情况下会使用单例模式?单例模式的使用场景是什么?
发面经攒人品
点赞 评论 收藏
分享
2025-11-27 22:23
已编辑
百度_前端(实习员工)
【11.21&nbsp;百度一面】:1:自我介绍2:小程序项目介绍(面试官比较感兴趣这个)3:实习项目简单问了一下4:可视化5:大文件上传整体的思路、断点续传具体怎么实现?6:讲一下transform怎么使用的(提到小程序里面的一个应用)7:react常见hook8:父组件A里面有一个子组件B,子组件B使用useMemo()缓存值,如果A重新渲染,会导致B中useMemo重新计算嘛?&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;(回答:分情况,要是缓存的值中有父组件传递的props就还是要)9:无限滚动加载和图片懒加载10:拦截器主要做了些什么工作?11:数组常用方法?12:你做了可视化大屏展示,现在有多个大屏,怎么适配各种尺寸呢?(答了媒体查询,为每一个尺寸范围适配不同的比例大小)追问:如果提前不知道大屏的宽度呢答:可以使用flex响应式布局,比如flex&nbsp;1&nbsp;自动填充剩下的内容区域13:说一下事件冒泡是什么,怎么阻止冒泡(用e.stopPropagation())14:手搓&nbsp;轮播图(太久没有写react了,面试官一直耐心指导,最后还是没搓出来,说了一下实现原理)15:反问业务、技术栈、实习生培养方案一面的面试官真的超级好,就我面试以来(七牛云、金蝶、懂车帝)是最好的一个了。一点一点指导,面试用的“如流”这个软件,中间断联了两次(一次网络断开,一次电脑没电直接关机,人傻了直接),结果面试官一点都没生气,还是很耐心的讲面完,10分钟不到,发消息,约二面...【11.24&nbsp;百度二面】1:自我介绍2:Promise哪些常用方法3:大文件上传4:手写Promise.all&nbsp;(有一点点小缺憾)5:懒加载怎么实现的&nbsp;(IntersationObserver)6:react的懒加载怎么实现(同上&nbsp;+&nbsp;React.lazy结合Suspense)7:react常用hook8:useCallback和useMemo说一下9:手撕一个react自定义hook:getPrevieusValue(value)&nbsp;传入一个值(state),获取他上一次的状态值(用的是map+数组存)面试官:其实可以用ref,因为你现在这个实现的话,依照的是状态,万一不小心改变了状态可能导致页面重新渲染8:3s后页面上显示啥?为啥?const&nbsp;app&nbsp;=&nbsp;()&nbsp;=&gt;&nbsp;{const&nbsp;[count,&nbsp;setCount]&nbsp;=&nbsp;useState(0)const&nbsp;[countTime,&nbsp;setCountTime]&nbsp;=&nbsp;useState(0)useEffect(()&nbsp;=&gt;&nbsp;{let&nbsp;timer&nbsp;=&nbsp;setTimeout(()&nbsp;=&gt;&nbsp;{setCountTime(count)},&nbsp;3000)setCount(5);()&nbsp;=&gt;&nbsp;clearTimeout(timer)},&nbsp;[])return&nbsp;(&lt;div&gt;count:{{&nbsp;count&nbsp;}}countTime:{{&nbsp;countTime&nbsp;}}&lt;/div&gt;)}(答了:分别是5和0,因为react状态是一个快照,他每计算次依照的是当前这一刻的状态(还好之前看过官方文档))9:上一个问题,那我想让都显示5怎么办?(提到用函数式更新、flushAsync、Ref持久化绑定)面试官:也可以在useEffect依赖里面加&nbsp;count10:redux使用过嘛?&nbsp;了解过基于redux开发的第三方工具嘛?(不了解)11:为啥使用redux不用useContext呢?12:说一下小程序的分包机制和分包预加载13:手写:数组转成树结构(没写出来[我算法太弱了[苦涩]],说了思路,用递归)const&nbsp;a&nbsp;=&nbsp;[{id:&nbsp;1,&nbsp;parent:&nbsp;null,&nbsp;name:&nbsp;'Big'},{id:&nbsp;2,&nbsp;parent:&nbsp;3,&nbsp;name:&nbsp;'hello'},{id:&nbsp;3,&nbsp;parent:&nbsp;1,&nbsp;name:&nbsp;'aloha'},{id:&nbsp;4,&nbsp;parent:&nbsp;1,&nbsp;name&nbsp;:&nbsp;'yes'}];const&nbsp;b&nbsp;=&nbsp;{id:&nbsp;1,name:&nbsp;'big',children:&nbsp;[{id:&nbsp;3,name:&nbsp;'aloha',children:&nbsp;[{id:&nbsp;2,name:&nbsp;'hello',children:&nbsp;null,},],},{id:&nbsp;4,name:&nbsp;'yes',children:&nbsp;null,},],};面试官说你思路没问题,然后就给我讲了一遍非递归的实现思路如流这个软件,不知道是不是不适配我的电脑还是啥,面试的时候一直掉线(掉了四五次),开始我还以为我的网有问题,不过好在面试官没有生气,而且手撕题还给我耐心纠错和指导,也是很好的一个面试官【11.27】hr电话正上体育课,刚测完1000m,累成憨憨了,摊在地上结果忽然收到hr的电话,oc,下午1点多快到2点的时候,收到正式offer邮件......【插话】本来二面结束(周一),感觉没戏了,周二到周四整个人都是一个将die的状态,浑身不舒服,也没胃口,觉也睡不好,期间还面了懂车帝、深圳锐明技术,懂车帝面成粑粑了、锐明面的很好,但是这俩截至目前都没有下文面试,运气真的很重要,要是遇到一个恶心人的面试官,神来了也得挂。唯一&nbsp;一点幸运的是,我做过小程序,刚好和他们组技术栈和业务匹配,同时干小程序的人本身也不多,所以被面试官看上了吧,要是投的其他业务组,一面都得挂
发面经攒人品
点赞 评论 收藏
分享
评论
2
7
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务