【Linux多线程】线程安全的单例模式

1. 单例模式 与 设计模式

1.1 单例模式

单例模式(Singleton Pattern)是一种创建型设计模式,确保一个类在整个应用程序中只有一个实例,并提供一个全局访问点来访问这个实例。这个模式适用于那些只需要一个实例来控制整个系统的场景,如配置管理、日志记录等。

关键特性:

  • 唯一性:确保类只有一个实例,避免多实例带来的资源浪费和状态不一致。
  • 全局访问:提供一个全局访问点,使得应用程序中的所有代码能够方便地访问这个唯一实例。
  • 延迟实例化:实现延迟实例化,即仅在第一次访问时创建实例,从而节省资源并提高启动效率。

典型实现(线程安全的单例模式):

class Singleton {
public:
    static Singleton& getInstance() {
        static Singleton instance; // 局部静态变量保证实例唯一性
        return instance;
    }

    // 删除拷贝构造函数和赋值运算符以防止复制
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

private:
    Singleton() {} // 私有构造函数以防止外部创建实例
};

1.2 设计模式

设计模式(Design Pattern)是面向对象设计中的一种标准化、经过验证的解决方案,用于解决常见的设计问题。

设计模式的类型

  1. 创建型模式:关注对象的创建过程。常见的有:

    • 单例模式(Singleton)
    • 工厂模式(Factory Method)
    • 抽象工厂模式(Abstract Factory)
    • 原型模式(Prototype)
    • 建造者模式(Builder)
  2. 结构型模式:关注类和对象如何组合成更大的结构。常见的有:

    • 适配器模式(Adapter)
    • 装饰器模式(Decorator)
    • 代理模式(Proxy)
    • 桥接模式(Bridge)
    • 组合模式(Composite)
    • 外观模式(Facade)
    • 享元模式(Flyweight)
  3. 行为型模式:关注对象之间的交互和责任分配。常见的有:

    • 观察者模式(Observer)
    • 策略模式(Strategy)
    • 状态模式(State)
    • 责任链模式(Chain of Responsibility)
    • 迭代器模式(Iterator)
    • 访问者模式(Visitor)
    • 模板方法模式(Template Method)
    • 命令模式(Command)
    • 解释器模式(Interpreter)
    • 中介者模式(Mediator)
    • 备忘录模式(Memento)

设计模式的目的:

  • 提高代码复用:通过采用通用的解决方案,减少代码重复,提高复用性。
  • 提高代码可维护性:设计模式提供了清晰的结构,使得代码更易于理解和维护,提升了整体可维护性。
  • 解决常见问题:设计模式为常见的设计问题提供了经过验证的解决方案,帮助开发人员更高效地解决问题。

1.3 饿汉实现模式 与 懒汉实现模式

单例模式分为饿汉实现 与 懒汉实现两种,简单举个例子:

饿汉方式:吃完饭,立刻洗碗;下一顿吃的时候可以立刻拿着碗就能吃饭.(确保资源随时可以使用)
懒汉方式:吃完饭, 先把碗放下, 然后下一顿饭用到这个碗了再洗碗(节省了不必要的操作,优化速度)

接下来我们对这两种单例实现模式进行解释:


1.4 饿汉模式

饿汉模式 在类被加载时就立即创建实例,而不是在第一次需要实例时才创建。这种方式的特点是实例创建时机早,通常用于资源不多且创建实例开销较小的情况。


① 饿汉模式的特点

  1. 立即实例化:类加载时就创建单例实例,不管是否有其他地方需要这个实例。
  2. 线程安全:由于实例在类加载时就创建好了,线程安全问题得到了天然的解决。
  3. 浪费资源:如果单例对象的创建开销较大,而实际运行时不一定会使用这个实例,则会浪费资源。

② 饿汉式单例模式的实现

下面是饿汉实现的单例模式的示例( C++ ):

class Singleton {
public:
    // 提供全局访问点
    static Singleton& getInstance() {
        return instance; // 直接返回已经创建好的实例
    }

    // 删除拷贝构造函数和赋值运算符以防止复制
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

private:
    // 构造函数是私有的,以防止外部创建实例
    Singleton() {}

    // 静态成员变量,类加载时就创建实例
    static Singleton instance;
};

// 在类外定义静态成员变量
Singleton Singleton::instance;

③ 饿汉式单例模式的优缺点

优点:

  • 线程安全:由于实例在程序启动时即被创建,因此不需要额外的同步机制来确保线程安全。
  • 简单易用:实现过程简单,易于理解和使用。

缺点:

  • 资源浪费:即使在程序运行过程中不需要这个单例实例,也会在启动时创建,可能导致资源浪费。
  • 不适合大开销对象:对于实例化开销较大的对象,预创建可能导致不必要的资源消耗。

④ 适用场景

  • 实例化对象开销较小且必定会被使用。
  • 对线程安全要求较高,且不介意在应用启动时立即创建实例。

1.5 懒汉模式

懒汉模式(Lazy Initialization) 是一种单例模式的实现方式,它在第一次需要实例时才创建对象。这种方式的特点是推迟实例化,从而避免不必要的资源消耗。

① 懒汉式单例模式的特点

  • 延迟实例化:对象仅在第一次被请求时才进行创建,从而提高了程序的效率。
  • 资源节省:在不需要实例的情况下,避免了资源的浪费,优化了资源使用。
  • 线程安全问题:在多线程环境下,可能需要额外的同步机制来确保线程安全,确保实例的唯一性。

② 懒汉式单例模式的实现

下面是懒汉模式的实现( C++ ):

#include <mutex>

class Singleton {
public:
    // 提供全局访问点
    static Singleton* getInstance() {
        if (!instance) { // 如果实例尚未创建
            std::lock_guard<std::mutex> guard(mutex); // 加锁
            if (!instance) { // 再次检查以避免多线程下的重复创建
                instance = new Singleton(); // 创建实例
            }
        }
        return instance;
    }

    // 删除拷贝构造函数和赋值运算符以防止复制
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

private:
    // 构造函数是私有的,以防止外部创建实例
    Singleton() {}

    // 静态成员变量,保存单例实例
    static Singleton* instance;
    // 互斥锁,保护实例化过程
    static std::mutex mutex;
};

// 在类外定义静态成员变量
Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mutex;

③ 懒汉式单例模式的优缺点

优点:

  • 节省资源:实例仅在实际需要时创建,从而避免了不必要的资源消耗。
  • 延迟加载:能够提升程序启动速度,尤其在创建单例对象开销较大的情况下尤为显著。

缺点:

  • 线程安全问题:在多线程环境下,可能需要额外的同步机制,如使用互斥锁(mutex),以避免竞态条件。
  • 实现复杂度:比起饿汉式单例实现更复杂,需要处理多线程环境下的同步问题。

④ 适用场景

  • 实例化开销较大或不一定需要使用实例时。
  • 需要延迟加载,优化资源使用。

2. 智能指针 与 线程安全

2.1 STL的容器是否是线程安全?

不是!

  1. STL 的设计初衷是尽可能的利用性能,进行高效的编码,一旦涉及到加锁保证线程安全, 会对性能造成巨大的影响.
  2. 对于不同的容器,加锁方式的不同,性能可能也不同(如hash表的锁表和锁桶)
  3. 因此 STL 默认不是线程安全. 如果需要在多线程环境下使用, 往往需要调用者自行保证线程安全

2.2 智能指针是否是线程安全?

  • 对于 unique_ptr,其只在当前代码块范围内生效, 因此不涉及线程安全问题.
  • 对于 shared_ptr, 多个对象需要共用一个引用计数变量,存在线程安全问题,但标准库实现时考虑到了该问题,基于原子操作(CAS)的方式保证 shared_ptr 能够高效, 原子的操作引用计数
  • 11
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值