单例模式

单例模式保证一个类只有一个实例并提供全局访问点。饿汉模式在类加载时即创建实例,线程安全;懒汉模式延迟创建,存在线程安全问题。为解决此问题,有单检查锁和双检查锁实现,双检查锁需配合memory_fence防止指令重排序。C++11的call_once函数也可确保代码只执行一次,实现线程安全的单例。
摘要由CSDN通过智能技术生成

单例模式(Singleton Pattern)是一种创建型设计模式,用于确保一个类只有一个实例,并提供全局访问点来访问该实例。单例模式通常用于需要共享资源的情况,例如数据库连接、日志记录器、缓存等

特点:

  • 一个类只有一个实例:单例模式确保一个类只有一个实例对象存在
  • 全局访问点:通过单例模式,可以在程序的任何地方访问该实例,而无需传递实例对象的引用

优点:

  • 对于频繁使用的对象,可以减少对象创建和销毁的开销,提高性能
  • 确保只有一个实例存在,避免了资源的重复占用和冲突
  • 提供了对唯一实例的控制,可以实现对该实例的灵活性管理

饿汉模式

饿汉模式的单例本身就是线程安全的:

class Singleton {
public:
    static Singleton* getInstance() {return m_instance;}
    static void deleteInstance() {
        if (m_instance) {
            delete m_instance;
            m_instance = nullptr;
        }
    }

private:
    Singleton() { std::cout << "constructor" << std::endl; }
    ~Singleton() { std::cout<< "destructor" << std::endl; }
    Singleton(const Singleton &signal) = delete;
    Singleton& operator=(const Singleton &signal) = delete;

private:
    static Singleton *m_instance;
};

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

int main () {
    Singleton* ps = Singleton::getInstance();
    Singleton::deleteInstance();
    return 0;
}

或者采用 static 局部变量的形式实现单例:

class Singleton {
public:
    static Singleton& getInstance() {
        static Singleton instance;
        return instance;
    }
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

private:
    Singleton() {}
};

懒汉模式-naive实现

class Singleton {
private:
    Singleton() { std::cout << "constructor" << std::endl; }
    ~Singleton() { std::cout<< "destructor" << std::endl; }
    Singleton(const Singleton& s) = delete;
    Singleton& operator=(const Singleton& singleton) = delete;
    static Singleton* m_instance;       //全局的唯一实例

public:
    static Singleton* getInstance() {
        if (m_instance == nullptr) {
            m_instance = new Singleton();
        }
        return m_instance;
    }
    static void deleteInstance() {
        if (m_instance) delete m_instance;
        m_instance = nullptr;
    }

};

int main () {
    Singleton* ps = Singleton::getInstance();
    Singleton::deleteInstance();
    return 0;
}
Singleton* Singleton::m_instance = nullptr;

这种实现对于单线程程序是可用的,但是一旦面临多线程,就会出现线程安全的问题。下面给出线程安全的版本


懒汉模式-单检查锁实现

class Singleton {
private:
    Singleton() { std::cout << "constructor" << std::endl; }
    ~Singleton() { std::cout<< "destructor" << std::endl; }
    Singleton(const Singleton& s) = delete;
    Singleton& operator=(const Singleton& singleton) = delete;
    static std::mutex m_mutex;
    static Singleton* m_instance;       //全局的唯一实例

public:
    static Singleton* getInstance() {
        std::unique_lock<std::mutex> lock(m_mutex);
        if (m_instance == nullptr) {
            m_instance = new Singleton();
        }
        return m_instance;
    }
    static void deleteInstance() {
        std::unique_lock<std::mutex> lock(m_mutex);
        if (m_instance != nullptr) delete m_instance;
        m_instance = nullptr;
    }
};
Singleton* Singleton::m_instance = nullptr;
std::mutex Singleton::m_mutex;

int main () {
    Singleton* ps = Singleton::getInstance();
    Singleton::deleteInstance();
    return 0;
}

这样做是线程安全的,但是每次调用 getInstance() 都需要加锁解锁,相当于将读取 m_instance 的操作进行了排队,极大地损失了效率。下面给出更高效的实现


懒汉模式-双检查锁实现

class Singleton {
private:
    Singleton() { std::cout << "constructor" << std::endl; }
    ~Singleton() { std::cout<< "destructor" << std::endl; }
    Singleton(const Singleton& s) = delete;
    Singleton& operator=(const Singleton& singleton) = delete;
    static std::mutex m_mutex;
    static Singleton* m_instance;       //全局的唯一实例

public:
    static Singleton* getInstance() {
        if (m_instance == nullptr) {	//只有当m_instance确实为空才用加锁
            std::unique_lock<std::mutex> lock(m_mutex);
            if (m_instance == nullptr) {
                m_instance = new Singleton();
            }
        }
        return m_instance;
    }
    static void deleteInstance() {
        std::unique_lock<std::mutex> lock(m_mutex);
        if (m_instance != nullptr) delete m_instance;
        m_instance = nullptr;
    }
};
Singleton* Singleton::m_instance = nullptr;
std::mutex Singleton::m_mutex;

int main () {
    Singleton* ps = Singleton::getInstance();
    Singleton::deleteInstance();
    return 0;
}

为什么要double check?

如果我们将上面的实现的第二个检查去掉,会导致什么问题:

static Singleton* getInstance() {
    if (m_instance == nullptr) {	//只有当m_instance确实为空才用加锁
        std::unique_lock<std::mutex> lock(m_mutex);
        m_instance = new Singleton();
    }
    return m_instance;
}

如果有两个线程都通过了if判断,那么他们都会经历 加锁-new对象-解锁 的过程,所以new会被执行两次,违背了单例模式的规则。所以我们需要在锁前和锁后检查两次

注意双检查锁看似是线程安全的,但实际并非如此

正常的new顺序:分配内存-执行构造函数-返回内存地址

但实际在指令层面,CPU可能打乱顺序:分配内存-返回内存地址-执行构造函数

这样,当一个线程执行完“分配内存-返回内存地址”,还没来得及调用构造函数时,切换到另一个线程执行,它发现 m_instance 不是null,于是返回一个没有构造完成的“对象”,导致了错误

这是编译器对指令进行reorder优化导致的问题,各个语言几乎都存在这个问题

C++的解决方案是加入 memory_fence

class Singleton {
private:
    Singleton() { std::cout << "constructor" << std::endl; }
    ~Singleton() { std::cout<< "destructor" << std::endl; }
    Singleton(const Singleton& s) = delete;
    Singleton& operator=(const Singleton& singleton) = delete;
    static std::mutex m_mutex;
    static std::atomic<Singleton*> m_instance;

public:
    static Singleton* getInstance() {
        Singleton* t = m_instance.load(std::memory_order_relaxed);
        std::atomic_thread_fence(std::memory_order_acquire);
        if (t == nullptr) {
            std::unique_lock<std::mutex> lock(m_mutex);
            t = m_instance.load(std::memory_order_relaxed);
            if (t == nullptr) {
                t = new Singleton();
                std::atomic_thread_fence(std::memory_order_release);
                m_instance.store(t, std::memory_order_relaxed);
            }
        }
        return t;
    }
    static void deleteInstance() {
        std::unique_lock<std::mutex> lock(m_mutex);
        if (m_instance != nullptr) delete m_instance;
        m_instance = nullptr;
    }
};
std::atomic<Singleton*> Singleton::m_instance;
std::mutex Singleton::m_mutex;

int main () {
    Singleton* ps = Singleton::getInstance();
    Singleton::deleteInstance();
    return 0;
}

懒汉模式-call_once 实现

call_once 是 C++ 11 提供的一个库函数,用于保证代码只被执行一次:

#include<mutex>
template <class Fn, class... Args>
void call_once(once_flag& flag, Fn&& fn, Args&&...args);
class Singleton {
private:
    Singleton() { std::cout << "constructor" << std::endl; }
    ~Singleton() { std::cout<< "destructor" << std::endl; }
    Singleton(const Singleton& s) = delete;
    Singleton& operator=(const Singleton& singleton) = delete;
    static Singleton* m_instance;       //全局的唯一实例
    static once_flag oc;

public:
    static Singleton* getInstance() {
        call_once(oc, init);
        return m_instance;
    }
    static void deleteInstance() {
        delete m_instance;
        m_instance = nullptr;
    }

    static void init() {
        if (m_instance == nullptr) {
            m_instance = new Singleton();
        }
    }

    void printLog(const string& s) {
        cout << s << endl;
    }
};
// 类中的静态成员必须在类外初始化
Singleton* Singleton::m_instance = nullptr;
once_flag Singleton::oc;

void printError() {
    auto s = Singleton::getInstance();
    s->printLog("fuck");
}

int main() {
    thread t1{printError};
    thread t2{printError};
    t1.join();
    t2.join();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值