单例,顾名思义是一个实例,即在一个项目中,单例的类只实例化一次。它常常应用于数据库操作,日志函数。在一个大型项目中使用到日志和数据库操作的地方很多,不能每个文件都去单独实例化一次,此时单例模式就显示出了它的价值。
单例的核心在类的内部方法__new__()
,每次实例化都是通过执行new函数来返回单例对象
单例就是在类里面定义而一个作用域最高的标志性的属性,如果实例化过一次,那么这个属性为True,否则为False,那么返回上次实例化的对象。
意图
保证一个类仅有一个实例,并提供一个访问它的全局访问点
适用性
当类只能有一个实例而且客户可以从一个众所周知的访问点访问它时。
当这个唯一实例应该是通过子类化可以扩展的,并且客户应该无需更改代码就能使用一个扩展的实例时。
单例模式的优点和应用
单例模式的优点:
1. 由于单例模式要求在全局内只有一个实例,因而可以节省较多的内存空间
2. 全局只有一个接入点,可以更好的进行数据同步控制,避免多钟占用。
3. 单例可常驻内存,减少系统开销。
单例模式的应用举例:
1. 生成全局唯一的序列号
2. 访问全局复用的唯一资源,如磁盘,总线等。
3. 单个对象占用的资源过多,如数据库等
4. 系统全局统一管理,如 windows 下的Task Manager
5. 网站计数器
单点模式的缺点
1. 单例模式的扩展是比较困难的。
2. 赋予了单例以太多的职责,某种程度上违反了单一职责原则
3. 单例模式是并发协作软件模式中需要最先完成的,因其不利于测试
4. 单例模式在某种情况下会导致"资源瓶颈"
核心代码
class Singleton(object):
def __new__(cls, *args, **kwargs):
if not hasattr(cls, '_instance'):
org = super(Singleton, cls) # 让 cls 继承指定的父类 Singleton
cls._instance = org.__new__(cls) # 创建实例
return cls._instance # 返回具体的实例
# 复写内部方法`__new__()`
# 通过`hasattr`函数判断该类实例化时有没有`_instance`属性
# 如果不存在,那么继承并返回原始的`__new__`方法给`_instance`属性
# 如果存在则直接返回`_instance`属性所指的对象
示例代码1:
class Singleton(object):
def __new__(cls, *args, **kwargs):
if not hasattr(cls, "_instance"):
org = super(Singleton, cls)
cls._instance = org.__new__(cls)
return cls._instance
classs MyClass(Singleton):
def __init__(self, name):
self.name = name
if __name__ == "__main__":
a = MyClass("A")
print(a.name)
b = MyClass("B")
print(a.name)
print(b.name)
b.name = "c"
print(a.name)
print(b.name)
示例代码2:
class Singleton(object):
def __init__(self):
print("__init__")
def __new__(cls, *args, **kwargs):
print("__new__")
if not hasattr(Singleton, "_instance"):
print("创建新的实例")
org = super(Singleton, cls)
Singleton._instance = org.__new__(cls)
return Singleton._instance
obj1 = Singleton()
obj2 = Singleton()
print(obj1, obj2)
输出结果:
__new__
创建新实例
__init__
__new__
__init__
<__main__.Singleton object at 0x7fc871f65da0> <__main__.Singleton object at 0x7fc871f65da0>
但这样其实有个小问题,看输出其实执行了两遍__init__()
方法,既然是同一个对象,初始化两次,这是不太合理的,我们可以改造一下
class Singleton(object):
def __init__(self):
if not hasattr(Singleton, "_first_init"):
print("__init__")
Singleton._first_init = True
def __new__(cls, *args, **kwargs):
print("__new__")
if not hasattr(Singleton, "_instance"):
print("创建新的实例")
org = super(Singleton, cls)
Singleton._instance = org.__new__(cls)
return Singleton._instance
obj1 = Singleton()
obj2 = Singleton()
print(obj1, obj2)
而且 __new__()
方法是支持多线程的,不需要单独在加线程锁进行规避操作。
装饰器实现单例
def Singleton(cls):
_instance = {} # 字典用来保存被装饰类的是实例对象
def _singleton(*args, **kwargs):
if cls not in _instance:
_instance[cls] = cls(*args, **kwargs)
return _instance[cls]
return _singleton
@Singleton
class A:
a = 1
def __init__(self, x=0):
self.x = x
a1 = A(2)
a2 = A(4)
print(id(a1), id(a2))
类方法实现
class Singleton(object):
def __init__(self, *args, **kwargs):
pass
@classmethod
def get_instance(cls, *args, **kwargs):
# hasattr() 函数用于判断对象是否包含对应的属性,这里是看看这个类有没有 _instance 属性
if not hasattr(Singleton, "_instance"):
Singleton._instance = Singleton(*args, **kwargs):
return Singleton._instance
s1 = Singleton() # 使用这种方式创建的实例的时候,并不能保证单例
s2 = Singleton.get_instance() # 只有使用这种方式创建的时候才可以实现单例
s3 = Singleton()
s4 = Singleton.get_instance()
print(id(s1), id(s2), id(s3), id(s4))
其实这种方式的思路就是,调用类的 get_instance()
方法去创建对象,get_instance
方法会判断之前有没有创建对象,有的话也是会返回之前已经创建的对象,不在新创建,但是这样有个弊端,就是在使用类创建s3 = Singleton()
这种方式的时候,就不能保证单例了,也就是说在创建类的时候一定要用类里规定的 get_instance()
方法创建。再者,当使用多线程时也会出现问题。
import threading
class Singleton(object):
def __init__(self, *args, **kwargs):
import time
time.sleep(1)
@classmethod
def get_instance(cls, *args, **kwargs):
# hasattr() 函数用于判断对象是否包含对应的属性,这里是看看这个类有没有 _instance 属性
if not hasattr(Singleton, "_instance"):
Singleton._instance = Singleton(*args, **kwargs)
return Singleton._instance
def task():
obj = Singleton.get_instance()
print(obj)
for i in range(10):
t = threading.Thread(target=task)
t.start()
<__main__.Singleton object at 0x031014B0>
<__main__.Singleton object at 0x00DA32F0>
<__main__.Singleton object at 0x03101430>
<__main__.Singleton object at 0x03101530>
<__main__.Singleton object at 0x031015B0>
<__main__.Singleton object at 0x031016B0>
<__main__.Singleton object at 0x03101630>
<__main__.Singleton object at 0x03101830>
<__main__.Singleton object at 0x03101730>
<__main__.Singleton object at 0x031017B0>
Process finished with exit code 0
如果在 __init__()
方法中有些IO操作(此处使用time.sleep(1)类模拟),就会发现此时并不是同一个实例对象,这里是因为在一个对象创建的过程中,会先去获取 _instance
属性,判断之前有没有实例对象,因为 IO 耗时操作,当他们判断的时候,还没有对象完成实例化,所以就会调用 init() 方法进行实例化,结果就是条用了多次,然后就创建了多个对象。那么如何解决呢?答案是加锁,在获取对象属性_instance
的时候加锁,如果已经有人在获取对象了,其他的人如果要获取这个对象就先等一下,因为前面的那个人,可能在创建对象,就是还没创建完成。代码如下
import threading
class Singleton(object):
_instance_lock = threading.Lock() # 线程锁
def __init__(self, *args, **kwargs):
import time
time.sleep(1)
@classmethod
def get_instance(cls, *args, **kwargs):
with Singleton._instance_lock:
# hasattr() 函数用于判断对象是否包含对应的属性,这里是看看这个类有没有 _instance 属性
if not hasattr(Singleton, "_instance"):
Singleton._instance = Singleton(*args, **kwargs)
return Sinleton._instance
但是为了保证线程安全,在类内部加入锁机制,又会使加锁部分代码串行执行,速度降低。
网友评论