单例模式

15 篇文章 0 订阅

目录

单例模式:

1)定义

2)实现方法:

3)优点和应用场景:

c++实现懒汉模式:

1)muduo C++网络库的线程安全单例模式:

使用方法:

2)单线程版:

3)各种多线程版:

a) 上锁(线程安全但效率不高)

b)双检查锁(DCL)(会导致未初始化的内存访问)

c)使用内存栅栏(线程安全)

饿汉模式:


单例模式:

1)定义

简单定义:保证这个类在程序里面只有一个实例存在。

单例模式的基本结构需满足以下要求。

  • 单例模式的核心结构只有一个单例类,单例模式要保证这个类在运行期间只能被实例化一次,即只会被创建唯一的一个单例类的实例。
  • 单例模式需要提供一个全局唯一能得到这个类实例的访问点,一般通过定义一个名称类似为GetInstance的公用方法实现这一目的。

单例模式的基本结构图:

1

2)实现方法:

实现一个单例模式需要:1>私有的构造函数。2>一个静态方法,返回这个唯一实例的引用。3>一个指针静态变量。4>选择一个解决多线程问题的方法。

1.把构造函数声明为私有,则只有Singleton类内的可以调用构造函数。

2.用静态方法来实例化这个对象,并返回这个对象。

3.利用一个静态变量来记录Singleton类的唯一实例。

4.解决多线程问题的方法见下面代码

单例模式又分为饿汉式单例和懒汉式单例。

饿汉式单例在单例类被加载时就实例化一个对象交给自己的引用;

而懒汉式在调用取得实例方法的时候才会实例化对象。

在C++ 中一般都使用懒汉式单例,但懒汉式单例可能会有线程安全问题

3)优点和应用场景:

  单例模式的优点:

    1. 在内存中只有一个对象,节省内存空间

    2. 避免频繁的创建销毁对象,可以提高性能

    3. 避免对共享资源的多重占用

    4. 可以全局访问

  单例模式的适用场景:

    1. 需要频繁实例化然后销毁的对象

    2. 创建对象耗时过多或者耗资源过多,但又经常用到的对象

    3. 有状态的工具类对象

    4. 频繁访问数据库或文件的对象

    5. 以及其他要求只有一个对象的场景

单例模式适用于各种系统中某个类的对象只能存在一个这样的场景

(比如进程池,linux高性能服务器p290)

c++实现懒汉模式:

1)muduo C++网络库的线程安全单例模式:

//pthread_once  api   pthread_once_t 必须是全局变量or静态变量
// 保证所有线程中 init_routine 只被调用一次
pthread_once_t once_control = PTHREAD_ONCE_INIT;

int pthread_once(pthread_once_t* once_control, void (*init_routine)(void));


//muduo c++ 网络库的单例模式
//singleton.h
template<typename T>
class Singleton : boost::noncopyable
{
public:
    static T& instance()
    {
        pthread_once(&ponce_, &Singleton::init);
        return *value_;
    }
private:
    Singleton();
    ~Singleton();

    static void init()
    {
        value_ = new T();
    }

private:
    static pthread_once_t ponce_;  
    static T* value_;
};

//注意:必须头文件中定义static变量 (模板类的静态变量是这样的,但是普通类的静态成员初始化在源文件中)

template <typename T>
pthread_once_t Singleton<T>::ponce_ = PTHREAD_ONCE_INIT;

template<typename T>
T* Singleton<T>::value_  = NULL;

//在C++中,std::call_once和std::once_flag可以代替上面的pthread_once和pthread_once_t。(直接换就行)


使用方法:

MyClass&  mc = Singleton<MyClass>::instance();

2)单线程版:

class Singleton{
private:
    Singleton();  // 私有默认构造函数
    Singleton(const Singleton& other);  // 私有拷贝构造函数

    static Singleton* m_instance;
	
public:
    static Singleton* getInstance();
    
};

Singleton* Singleton::m_instance=nullptr;

//线程非安全版本
Singleton* Singleton::getInstance() {
    if (m_instance == nullptr) {
        m_instance = new Singleton();
    }
    return m_instance;
}

3)各种多线程版:

a) 上锁(线程安全但效率不高)

class Singleton{
private:
    Singleton();  // 私有默认构造函数
    Singleton(const Singleton& other);  // 私有拷贝构造函数

    static Singleton* m_instance;
	static pthread_mutex_t mutex; // 互斥锁
public:
    static Singleton* getInstance();
    
};

Singleton *Singleton::m_instance=nullptr;
pthread_mutex_t Singleton::mutex= PTHREAD_MUTEX_INITIALIZER; // 静态的互斥锁,初始方法


//线程安全版本,但锁的代价过高
Singleton* Singleton::getInstance() {
    pthread_mutex_lock(&mutex);
    if (m_instance == nullptr) {
        m_instance = new Singleton();
    }
	pthread_mutex_unlock(&mutex);
	
    return m_instance;
}

b)双检查锁(DCL)(会导致未初始化的内存访问)

由于指令乱序执行,可能导致线程A先进入,在执行m_instance = new Singleton()的时候,先分配内存,再赋值给m_instance,最后执行Singleton的构造函数。但是在赋值之后,未初始化前,另一线程B可能通过getInstance获取到这个指针,导致线程B拿到的是一个指向未初始化的对象。

//双检查锁,但由于内存读写reorder不安全
Singleton* Singleton::getInstance() {
    
    if(m_instance==nullptr){
        pthread_mutex_lock(&mutex);
        if (m_instance == nullptr) {
            m_instance = new Singleton();
        }
		pthread_mutex_unlock(&mutex);
    }
    return m_instance;
}

c)使用内存栅栏(线程安全)

//C++ 11版本之后的跨平台实现 (volatile)  内存栅栏
std::atomic<Singleton*> Singleton::m_instance;
std::mutex Singleton::m_mutex;

Singleton* Singleton::getInstance() {
    Singleton* tmp = m_instance.load(std::memory_order_relaxed);
    std::atomic_thread_fence(std::memory_order_acquire);//获取内存fence
    if (tmp == nullptr) {
        std::lock_guard<std::mutex> lock(m_mutex);
        tmp = m_instance.load(std::memory_order_relaxed);
        if (tmp == nullptr) {
            tmp = new Singleton;
            std::atomic_thread_fence(std::memory_order_release);//释放内存fence
            m_instance.store(tmp, std::memory_order_relaxed);
        }
    }
    return tmp;
}

饿汉模式:

饿汉:饿了肯定要饥不择食。所以在单例类定义的时候就进行实例化。(本身就是线程安全的,如下例子)

关于如何选择懒汉和饿汉模式:

特点与选择:

  懒汉:在访问量较小时,采用懒汉实现。这是以时间换空间。

  饿汉:由于要进行线程同步,所以在访问量比较大,或者可能访问的线程比较多时,采用饿汉实现,可以实现更好的性能。这是以空间换时间。

class Singleton{
private:
    Singleton();  // 私有默认构造函数
    Singleton(const Singleton& other);  // 私有拷贝构造函数

    static Singleton* m_instance;
	
public:
    static Singleton* getInstance();
    
};

// 饿汉模式的关键:初始化即实例化
Singleton* Singleton::m_instance=new Singleton(); 

//饿汉模式本身就是线程安全的
Singleton* Singleton::getInstance() {

    return m_instance;
}

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值