zoukankan      html  css  js  c++  java
  • 单例模式及改进Python代码实现

    一、说明

    1、定义:

    单例模式是所有设计模式中比较简单的一类,其定义如下:Ensure a class has only one instance, and provide a global point of access to it.(保证某一个类只有一个实例,而且在全局只有一个访问点)。

    2、优缺点

    优点:
    1、由于单例模式要求在全局内只有一个实例,因而可以节省比较多的内存空间;
    2、全局只有一个接入点,可以更好地进行数据同步控制,避免多重占用;
    3、单例可长驻内存,减少系统开销。
    缺点:
    1、单例模式的扩展是比较困难的;
    2、赋于了单例以太多的职责,某种程度上违反单一职责原则

    3、场景

    最常用的实例就是当一个office文档已经被打开时,通过另外一个窗口再次访问时,系统会提示已经被此文档已经被占用,只能以只读方式打开。

    二、代码实现

    1、方式一:常规方式实现单例模式

    # 方式一
    class Singleton(object):
        instance = None
        def __init__(self, name):
            self.name = name
        def __new__(cls, *args, **kwargs):  # 返回空对象
            if cls.instance:  # 是否已创建过对象
                return cls.instance  # 如果有,则直接返回
            cls.instance = object.__new__(cls)  # 没有则创建空对象,返回该空对象
            return cls.instance
    
    obj1 = Singleton("asd")
    obj2 = Singleton("a")
    print(obj2.name, obj1.name)
    print(id(obj1)==id(obj2))
    

    以上为类Singleton添加关键属性instance,该属性用于如果为空表示该类还未创建实例,如果不为空,则说明已经该类已经实例化过。__new__方法会创建一个Singleton的空对象,在创建过程中添加判断类属性instance是否已绑定对象实例的逻辑。
    以上代码执行结果如下:

    a a
    True
    

    两个obj具有同样的name属性和地址,说明是一个对象

    2、方式二:继承单例类型

    为了满足自定义一些方法,扩展性更高,使用继承修改一下单例模式

    # 方式二
    
    class Singleton(object):
        instance = None
        def __init__(self, name):
            self.name = name
        def __new__(cls, *args, **kwargs):
            # 返回空对象
            if cls.instance:
                return cls.instance
            cls.instance = object.__new__(cls)  # 创建空对象
            return cls.instance
    
    
    # 继承方式改写单例模式
    class MySingleton(Singleton):
    
        def func(self):
            pass
    
    obj3 = MySingleton("my singleton")
    obj4 = MySingleton("my singleton")
    print(id(obj4)==id(obj3))  # True
    

    3、方式三:多线程改写单例模式

    以上两种方式属于虽然功能正常,但是在多线程情景下,可能存在由于单例类创建空对象的时候非原子操作,所以一个线程创建单例调用__new__方法返回空对象的过程中,其他线程也在执行,也就是出现了多个线程同时拿到了Singleton.instance=None的情形,因而返回了多个不同的新的空对象,导致单例模式失效。代码如下

    import time
    import threading
    
    
    # 单例模式常规写法多线程下的问题
    class Singleton(object):
        instance = None
    
        def __init__(self, name):
            self.name = name
    
        def __new__(cls, *args, **kwargs):
            if cls.instance:
                return cls.instance
            time.sleep(0.01)  # 放慢线程创建空对象的速度
            cls.instance = object.__new__(cls)
            return cls.instance
    
    
    def task(n):
        obj = Singleton("singleton by %dth thread" % n)
        print("thread %s, id=%s" % (obj.name, id(obj)))
    
    for i in range(10):
        t = threading.Thread(target=task, args=(i+1,))
        t.start()
    

    以上引入threading模块创建多个线程,在__new__方法中使用time.sleep(0.01)语句阻塞单个线程创建空对象从而模拟极端情况。
    执行结果如下

    singleton by 1th thread, id=2475130212192
    singleton by 2th thread, id=2475130212248
    singleton by 4th thread, id=2474978871000
    singleton by 6th thread, id=2474978871392
    singleton by 5th thread, id=2475128184224
    singleton by 3th thread, id=2474978871336
    singleton by 7th thread, id=2474978871000
    singleton by 9th thread, id=2475128184224
    singleton by 10th thread, id=2474978871336
    singleton by 8th thread, id=2474978871392
    

    可看到不同的线程创建单例顺序不同,地址也不同。单例模式失效。
    究其原因是因为,__new__方法可以由多个线程同时执行,所以可以使用锁机制控制。代码如下方式三

    # 方式三:多线程版本下的单例模式
    import time
    import threading
    
    
    lock = threading.RLock()
    
    
    # 多线程情景下的单例模式
    class Singleton(object):
        instance = None
        
        def __init__(self, name):
            self.name = name
    
        def __new__(cls, *args, **kwargs):
    
            with lock:
                if cls.instance:
                    return cls.instance
                time.sleep(0.1) 
                cls.instance = object.__new__(cls)  # 创建空对象
                return cls.instance
    
    
    def task(n):
        obj = Singleton("%dth singleton" % n)
        print("%s, id=%s" % (obj.name, id(obj)))
    
    
    for i in range(10):
        t = threading.Thread(target=task, args=(i+1,))
        t.start()
    
    

    如上,采用锁机制解决问题资源同步和数据一致性的问题。当多个线程尝试创建对象的时候,都会先请求锁,拿到锁则执行,否则只能等待。其实等于变相使用锁机制控制多线程对同一资源instance类变量的访问。
    代码结果如下:

    singleton by 1th thread, id=2090604208704
    singleton by 2th thread, id=2090604208704
    singleton by 3th thread, id=2090604208704
    singleton by 4th thread, id=2090604208704
    singleton by 5th thread, id=2090604208704
    singleton by 6th thread, id=2090604208704
    singleton by 7th thread, id=2090604208704
    singleton by 8th thread, id=2090604208704
    singleton by 9th thread, id=2090604208704
    singleton by 10th thread, id=2090604208704
    

    以上使用线程锁,采用with上下文管理管理的方式,同样可以使用手动获取锁,释放的方式实现如下。

    lock.acquire()
    if cls.instance:
        return cls.instance
    time.sleep(0.1) 
    cls.instance = object.__new__(cls)  # 创建空对象
    return cls.instance
    lock.release()
    

    Python中的线程锁:

    • RLock:threading.RLock(),支持锁的嵌套。
    • Lock:threading.RLock(),不支持锁的嵌套,效率比RLock稍微高一些。

    如果一个函数内部操作用了Lock,那么再调用该函数就无法再上锁了,因此虽然RLock资源消耗稍微高一点,但是用Rlock多一些。

    4、方式四:优化后多线程下的单例模式

    # 方式四:优化后多线程下的单例模式
    class Singleton(object):
        instance = None
        lock = threading.RLock()
    
        def __init__(self, name):
            self.name = name
    
        def __new__(cls, *args, **kwargs):
            # 性能优化一点
            if cls.instance:
                return cls.instance
    
            with cls.lock:
                if cls.instance:
                    return cls.instance
                cls.instance = object.__new__(cls)
                return cls.instance
    

    以上方式四与方式三改进的两点在于:
    一:lock锁应该作为单例类型的类属性,因此将lock = threading.RLock()放到了类中。
    二:在__new__方法中添加if cls.instance: return cls.instance使得后续再重新创建实例如new_obj=Singleton("new obj")的时候,不需要再进入with cls.lock:代码逻辑中耗费一次锁资源。

  • 相关阅读:
    学习些新东西
    浏览器内的web开发工具
    基于oracle开发的初步接触
    LAMP3 PHP安装
    svn for windows
    PHP替换掉字符串中的非字符
    搭个邮件服务器
    centos下安装mysql
    安装tomcat
    c#线程
  • 原文地址:https://www.cnblogs.com/welan/p/15703117.html
Copyright © 2011-2022 走看看