zoukankan      html  css  js  c++  java
  • 【设计模式】单例模式

    单例模式

    简介

    单例模式LOGO

    你能在电脑上调出两个Windows任务管理器吗?假设能,如果两个管理器显示的数据相同,那何必要存在两个呢?如果两个管理器显示的数据不同,那我该相信哪一个呢?

    单例模式:确保一个类只有一个实例,并提供一个全局访问点来访问这个唯一实例。

    它的三个要点分别为:

    • 这个类只能有一个实例
    • 它必须自己创建这个实例
    • 它必须自己向整个系统提供这个实例

    结构

    单例模式UML

    实现

    // singleton.h
    
    #ifndef __SINGLETON_H__
    #define __SINGLETON_H__
    
    #include <iostream>
    #include <string>
    #include <thread>
    #include <chrono>
    #include <mutex>
    #include <pthread.h>
    #include <unistd.h>
    
    // 单线程版本
    class Singleton {
    public:
        static Singleton* getInstance() {   // 提供外部接口,满足了第三个要点
            if(instance == nullptr) {
                std::cout << "New Singleton" << std::endl;
                instance = new Singleton();
            }
            return instance;
        }
    private:
        Singleton(){}   // 构造函数是私有的,即只能在类内部实例化,满足了第二个要点
        static Singleton* instance; // instance是static的,满足了第一个要点
    };
    
    Singleton* Singleton::instance = nullptr;
    
    
    // 线程安全的多线程版本
    class SingletonA {
    public:
        static SingletonA* getInstance() {
            if(instance == nullptr) {
                m_mutex.lock(); // 添加互斥锁
                // std::lock_guard<std::mutex> lock(m_mutex);
                if(instance == nullptr) {
                    std::cout << "New SingletonA" << std::endl;
                    instance = new SingletonA();
                }
                m_mutex.unlock();
            }
            return instance;
        }
    
    private:
        SingletonA() {}
        static SingletonA* instance;
        static std::mutex m_mutex;
    };
    
    SingletonA* SingletonA::instance = nullptr;
    std::mutex SingletonA::m_mutex;
    
    #endif  //__SINGLETON_H__
    
    // main.cpp
    
    #include "singleton.h"
    
    void* CallSingleton(void *arg) {
        Singleton::getInstance();
        std::cout << "num: " << arg << std::endl;
        return nullptr;
    }
    
    void* CallSingletonA(void *arg) {
        SingletonA::getInstance();
        std::cout << "num: " << arg << std::endl;
        return nullptr;
    }
    
    int main(int argc, char *argv[]) {
        CallSingleton(0);
        CallSingleton(0);
    
        CallSingletonA(0);
        CallSingletonA(0);
    
        // Windows
        std::thread t1(CallSingleton);
        std::thread t2(CallSingleton);
        t1.join();
        t2.join();
    
        // Linux
        // pthread_t tid[2];
        // pthread_create(&tid[0], NULL, CallSingleton, NULL);
        // pthread_create(&tid[1], NULL, CallSingleton, NULL);
    
        return 0;
    }
    
    # singleton.py
    
    from threading import Thread, Lock
    
    
    # 单线程版本,使用元类
    class SingletonMeta(type):
    
        _instance = {}
    
        def __call__(cls, *args, **kw):
            if cls not in cls._instance:
                instance = super().__call__(*args, **kw)
                cls._instance[cls] = instance
            return cls._instance[cls]
    
    
    class TestSingleton(metaclass=SingletonMeta):
        def some_business_logic(self):
            # ...
            pass
    
    
    # 线程安全的多线程版本,使用元类
    class SingletonMateA(type):
    
        _instance = {}
        _lock: Lock = Lock()
    
        def __call__(cls, *args, **kw):
            with cls._lock:
                if cls not in cls._instance:
                    instance = super().__call__(*args, **kw)
                    cls._instance[cls] = instance
            return cls._instance[cls]
    
    
    class TestSingletonA(metaclass=SingletonMateA):
        value :str = None
    
        def __init__(self, value: str) -> None:
            self.value = value
    
        def some_business_logic(self):
            # ...
            pass
    
    
    def TestTestSingletonA(value: str) -> None:
        rsa = TestSingletonA(value)
        print(rsa.value)
    
    
    if __name__ == "__main__":
        #
        s1 = TestSingleton()
        s2 = TestSingleton()
        if id(s1) == id(s2):
            print(id(s1))
        else:
            print(id(s1), id(s2))
    
        #
        process1 = Thread(target=TestTestSingletonA, args=("FOO",))
        process2 = Thread(target=TestTestSingletonA, args=("BAR",))
        process1.start()
        process2.start()
    

    实例

    问题描述

    总线是计算机各种功能部件或者设备之间传送数据、控制信号等信息的公共通信解决方案之一。现假设有如下场景:某中央处理器(CPU)通过某种协议总线与一个信号灯相连,信号灯有64种颜色可以设置,中央处理器上运行着三个线程,都可以对这个信号灯进行控制,并且可以独立设置该信号灯的颜色。抽象掉协议细节(用打印表示),如何实现线程对信号等的控制逻辑。

    问题解答

    加线程锁进行控制,无疑是最先想到的方法,但各个线程对锁的控制,无疑加大了模块之间的耦合。较好的方法是使用单例模式:

    
    
    # example.py
    
    # encoding=utf8
    import threading
    import time
    
    
    # 这里使用方法__new__来实现单例模式
    class Singleton(object):  # 抽象单例
        def __new__(cls, *args, **kw):
            if not hasattr(cls, '_instance'):
                orig = super(Singleton, cls)
                cls._instance = orig.__new__(cls, *args, **kw)
            return cls._instance
    
    
    # 总线
    class Bus(Singleton):
        lock = threading.RLock()
    
        def sendData(self, data):
            self.lock.acquire()
            time.sleep(3)
            print("Sending Signal Data...", data)
            self.lock.release()
    
    
    # 线程对象,为更加说明单例的含义,这里将Bus对象实例化写在了run里
    class VisitEntity(threading.Thread):
        my_bus = ""
        name = ""
    
        def getName(self):
            return self.name
    
        def setName(self, name):
            self.name = name
    
        def run(self):
            self.my_bus = Bus()
            self.my_bus.sendData(self.name)
    
    
    if __name__ == "__main__":
        for i in range(3):
            print("Entity %d begin to run..." % i)
            my_entity = VisitEntity()
            my_entity.setName("Entity_"+str(i))
            my_entity.start()
    

    总结

    优点

    • 保证一个类只有一个实例,可以节省较多的内存空间。
    • 为实例提供一个全局访问点,可以更好地进行数据同步控制,避免多重占用。
    • 可以常驻内存,可以减少系统开销。

    缺点

    • 该模式同时解决了两个问题(保证一个类只有一个实例、为实例提供一个全局访问点),违反了单一职责原则。
    • 多线程下需要考虑线程安全机制。
    • 单例模式没有抽象层,不方便扩展。

    场景

    • 生成全局惟一的序列号。
    • 访问全局复用的惟一资源,如磁盘、总线等。
    • 单个对象占用的资源过多,如数据库等。
    • 系统全局统一管理,如Windows下的Task Manager
    • 网站计数器。

    与其他模式的关系

    • 外观模式类通常可以转换为单例模式类,因为在大部分情况下一个外观对象就够了。
    • 如果你能将对象的所有共享状态简化为一个享元对象, 那么享元模式就和单例模式类似了。 但这两个模式有两个根本性的不同。
      • 只会有一个单例实体, 但是享元类可以有多个实体, 各实体的内在状态也可以不同。
      • 单例对象可以是可变的。 享元对象是不可变的。
    • 抽象工厂模式生成器模式原型模式都可以用单例模式来实现。
  • 相关阅读:
    [剑指Offer] 10.矩形覆盖
    [剑指Offer] 9.变态跳台阶
    [剑指Offer] 8.跳台阶
    [剑指Offer] 7.斐波那契数列
    ArtifactTransferException: Failure to transfer org.apache.openejb:javaee-api:jar:5.0-1
    -Dmaven.multiModuleProjectDirectory system property is not set. Check $M2_HO 解决办法
    java中判断list是否为空的用法
    PL/SQL快速选中一行并执行
    substring的用法
    Oracle---------sql 中取值两列中值最大的一列
  • 原文地址:https://www.cnblogs.com/parzulpan/p/13496610.html
Copyright © 2011-2022 走看看