单例模式可以保证一个类仅有一个实例,并提供一个访问它的全局访问点。
适用于当类只能有一个实例而且客户可以从一个众所周知的访问点访问它,例如访问数据库、MQ等。
对于系统中的某些类来说,只有一个实例很重要,例如,一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务;一个系统只能有一个窗口管理器或文件系统;一个系统只能有一个计时工具或ID(序号)生成器。如在Windows中就只能打开一个任务管理器。如果不使用机制对窗口对象进行唯一化,将弹出多个窗口,如果这些窗口显示的内容完全一致,则是重复对象,浪费内存资源;如果这些窗口显示的内容不一致,则意味着在某一瞬间系统有多个状态,与实际不符,也会给用户带来误解,不知道哪一个才是真实的状态。因此有时确保系统中某个对象的唯一性即一个类只能有一个实例非常重要。
python的模块在第一次被导入时会执行一遍这个模块的代码,之后就不再执行。针对这个特性,可以实现单例。
# test.py
class SingleTon(object):
def __init__(self, name):
self.name = name
def run(self):
print(self.name)
s = SingleTon(name='vth')
# main.py
from test import s as s1
from test import s as s2
def show():
print(s1, id(s1))
print(s2, id(s2))
show()
输出:
<test.SingleTon object at 0x10b20dee0> 4481670880
<test.SingleTon object at 0x10b20dee0> 4481670880
可以证明只拿到了一个对象。
import threading
import time
def show():
time.sleep(1)
from test import s
print(s, id(s))
def main():
l = []
for i in range(3):
l.append(threading.Thread(target=show))
for i in l:
i.start()
if __name__ == '__main__':
main()
new是创造实例的方法,init是初始化实例的方法。new是类的方法,init是实例的方法。
class SingleTon:
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def get_instance():
s = SingleTon()
print(s, id(s))
if __name__ == '__main__':
get_instance()
get_instance()
输出:
<__main__.SingleTon object at 0x10d7d2ee0> 4521275104
<__main__.SingleTon object at 0x10d7d2ee0> 4521275104
这在单线程中工作正常。但分析一下代码,如果是多线程情况下,判断完cls._isinstance是None的时候,如果切换到别的线程,那就会创造多个实例。
import threading
import time
class SingleTon:
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
time.sleep(1)
cls._instance = super().__new__(cls)
return cls._instance
def get_instance():
s = SingleTon()
print(s, id(s))
def main():
l = []
for i in range(3):
l.append(threading.Thread(target=get_instance))
for i in l:
i.start()
if __name__ == '__main__':
main()
输出:
<__main__.SingleTon object at 0x104775ca0><__main__.SingleTon object at 0x104892040> 4371062848
<__main__.SingleTon object at 0x1048f73d0>4369898656
4371477456
这明显线程不安全。可以加个锁。继续测试。
import threading
import time
class SingleTon:
_instance = None
_lock = threading.Lock()
def __new__(cls, *args, **kwargs):
with cls._lock:
if cls._instance is None:
time.sleep(1)
cls._instance = super().__new__(cls)
return cls._instance
def get_instance():
s = SingleTon()
print(s, id(s))
def main():
l = []
for i in range(3):
l.append(threading.Thread(target=get_instance))
for i in l:
i.start()
if __name__ == '__main__':
main()
输出:
<__main__.SingleTon object at 0x10caf2ca0><__main__.SingleTon object at 0x10caf2ca0><__main__.SingleTon object at 0x10caf2ca0> 4507774112
4507774112
4507774112
可能会有疑问,为什么锁加在了if语句的外面。
这是因为,如果锁加在了if里面,那么3个线程都有可能进入if块。进入if块就必然要执行创建新实例,加跟没加一样。
# 单线程测试代码
class SingleTon:
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self, name):
self.name = name
def get_instance(args):
s = SingleTon('vth'+str(args))
print(s, id(s))
print(s.name)
if __name__ == '__main__':
get_instance('da')
get_instance('db')
输出:
<__main__.SingleTon object at 0x10fd29ee0> 4560428768
vthda
<__main__.SingleTon object at 0x10fd29ee0> 4560428768
vthdb
# 多线程测试代码
import threading
import time
class SingleTon:
_instance = None
_lock = threading.Lock()
def __new__(cls, *args, **kwargs):
with cls._lock:
if cls._instance is None:
time.sleep(1)
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self, name):
self.name = name
def get_instance(args):
s = SingleTon(str(args))
print(s, id(s))
print(s.name)
def main():
l = []
for i in range(3):
l.append(threading.Thread(target=get_instance, args=(i,)))
for i in l:
i.start()
if __name__ == '__main__':
main()
输出:
<__main__.SingleTon object at 0x10b633ca0> 4486020256
0
<__main__.SingleTon object at 0x10b633ca0> 4486020256
<__main__.SingleTon object at 0x10b633ca0> 4486020256
2
2
可见,虽然我们用锁的方式实现了线程安全的实例,但这个实例可被多次初始化(即多次调用了init方法)。那么,我们怎么限制只能调用一次init方法呢?
# 单线程
class SingleTon:
_instance = None
_flag = False
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self, name):
if not SingleTon._flag:
SingleTon._flag = True
self.name = name
def get_instance(args):
s = SingleTon('vth'+str(args))
print(s, id(s))
print(s.name)
if __name__ == '__main__':
get_instance('da')
get_instance('db')
输出:
<__main__.SingleTon object at 0x103cc1970> 4358674800
vthda
<__main__.SingleTon object at 0x103cc1970> 4358674800
vthda
看,在单线程中工作良好。但在多线程中可不行。
# 多线程
import threading
import time
class SingleTon:
_instance = None
_lock = threading.Lock()
_flag = False
def __new__(cls, *args, **kwargs):
with cls._lock:
if cls._instance is None:
time.sleep(1)
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self, name):
if not SingleTon._flag:
time.sleep(1)
SingleTon._flag = True
self.name = name
def get_instance(args):
s = SingleTon(str(args))
print(s, id(s))
print(s.name)
def main():
l = []
for i in range(3):
l.append(threading.Thread(target=get_instance, args=(i,)))
for i in l:
i.start()
if __name__ == '__main__':
main()
输出:
<__main__.SingleTon object at 0x10fd41ca0> 4560526496
0
<__main__.SingleTon object at 0x10fd41ca0> 4560526496
1
<__main__.SingleTon object at 0x10fd41ca0> 4560526496
2
分析一下原因,为啥在多线程中不行?
原因还是一样的。它是非线程安全的。如果有N个线程都进入到if not SingleTon._flag块中,那么它其中的代码都会执行,就实例化了多次。
所以依然要加锁。
import threading
import time
class SingleTon:
_instance = None
_lock = threading.Lock()
_flag = False
def __new__(cls, *args, **kwargs):
with cls._lock:
if cls._instance is None:
time.sleep(1)
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self, name):
with SingleTon._lock:
if not SingleTon._flag:
time.sleep(1)
SingleTon._flag = True
self.name = name
def get_instance(args):
s = SingleTon(str(args))
print(s, id(s))
print(s.name)
def main():
l = []
for i in range(3):
l.append(threading.Thread(target=get_instance, args=(i,)))
for i in l:
i.start()
if __name__ == '__main__':
main()
输出:
<__main__.SingleTon object at 0x1031d8ca0><__main__.SingleTon object at 0x1031d8ca0> 4347235488 4347235488
1
<__main__.SingleTon object at 0x1031d8ca0> 4347235488
11
完美,撒花!
装饰器的作用是在一段逻辑之前做点事情,之后做点事情。我们可以通过这个特性去实现单例。
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 Demo:
a = 1
def __init__(self, x=0):
self.x = x
d1 = Demo(1)
d2 = Demo(2)
print(id(d1), d1.x)
print(id(d2), d2.x)
输出:
4431961824 1
4431961824 1
根据我们的经验,这肯定是非线程安全的。
写个代码验证一下。
import threading
import time
def singleton(cls):
_instance = {}
def _singleton(*args, **kwargs):
if cls not in _instance:
time.sleep(1)
_instance[cls] = cls(*args, **kwargs)
return _instance[cls]
return _singleton
@singleton
class Demo:
a = 1
def __init__(self, x=0):
self.x = x
def get_instance(args):
d = Demo(args)
print(d.x)
def main():
l = []
for i in range(3):
l.append(threading.Thread(target=get_instance, args=(i,)))
for i in l:
i.start()
if __name__ == '__main__':
main()
看下输出:
01
2
import threading
import time
def singleton(cls):
# 其内存储 k:v k为类、v为实例
_instance = {}
_lock = threading.Lock()
def _singleton(*args, **kwargs):
with _lock:
if cls not in _instance:
time.sleep(1)
_instance[cls] = cls(*args, **kwargs)
return _instance[cls]
return _singleton
@singleton
class Demo:
a = 1
def __init__(self, x=0):
self.x = x
def get_instance(args):
d = Demo(args)
print(d.x)
def main():
l = []
for i in range(3):
l.append(threading.Thread(target=get_instance, args=(i,)))
for i in l:
i.start()
if __name__ == '__main__':
main()
看下输出:
00
0