单例模式总结

总结自: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
}

全部评论

相关推荐

ResourceUtilization:算法很难了,现在都需要相关论文还有对应的实习,可以先试试中厂
点赞 评论 收藏
分享
复制粘贴骂ai!
聪明的加菲猫又在摸鱼:我写论文也是这样,不断教育ai
点赞 评论 收藏
分享
评论
1
6
分享

创作者周榜

更多
牛客网
牛客企业服务