单例模式总结

总结自: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道真题和解析
点赞 评论 收藏
分享
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;行的命令怎么写?查看某个关键字对应的日志用什么命令?设计模式用的多吗?用了哪些?策略模式和工厂模式是怎么实现的?具体应用场景是什么?什么情况下会使用单例模式?单例模式的使用场景是什么?
发面经攒人品
点赞 评论 收藏
分享
评论
2
7
分享

创作者周榜

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