單例模式
單例模式就是確保一個類只有一個實例.當(dāng)你希望整個系統(tǒng)中,某個類只有一個實例時,單例模式就派上了用場.
比如,某個服務(wù)器的配置信息存在在一個文件中,客戶端通過AppConfig類來讀取配置文件的信息.如果程序的運行的過程中,很多地方都會用到配置文件信息,則就需要創(chuàng)建很多的AppConfig實例,這樣就導(dǎo)致內(nèi)存中有很多AppConfig對象的實例,造成資源的浪費.其實這個時候AppConfig我們希望它只有一份,就可以使用單例模式.
實現(xiàn)單例模式的幾種方法
1. 使用模塊
其實,python的模塊就是天然的單例模式,因為模塊在第一次導(dǎo)入的時候,會生成.pyc文件,當(dāng)?shù)诙螌?dǎo)入的時候,就會直接加載.pyc文件,而不是再次執(zhí)行模塊代碼.如果我們把相關(guān)的函數(shù)和數(shù)據(jù)定義在一個模塊中,就可以獲得一個單例對象了.
新建一個python模塊叫singleton,然后常見以下python文件
mysingleton.py
class Singleton(object):
def foo(self):
pass
singleton = Singleton()
使用:
from singleton.mysingleton import singleton
2. 使用裝飾器
裝飾器里面的外層變量定義一個字典,里面存放這個類的實例.當(dāng)?shù)谝淮蝿?chuàng)建的收,就將這個實例保存到這個字典中.
然后以后每次創(chuàng)建對象的時候,都去這個字典中判斷一下,如果已經(jīng)被實例化,就直接取這個實例對象.如果不存在就保存到字典中.
# encoding:utf-8
__author__ = 'Fioman'
__time__ = '2019/3/6 10:22'
def singleton(cls):
# 單下劃線的作用是這個變量只能在當(dāng)前模塊里訪問,僅僅是一種提示作用
# 創(chuàng)建一個字典用來保存類的實例對象
_instance = {}
def _singleton(*args, **kwargs):
# 先判斷這個類有沒有對象
if cls not in _instance:
_instance[cls] = cls(*args, **kwargs) # 創(chuàng)建一個對象,并保存到字典當(dāng)中
# 將實例對象返回
return _instance[cls]
return _singleton
@singleton
class A(object):
a = 1
def __init__(self, x=0):
self.x = x
print('這是A的類的初始化方法')
a1 = A(2)
a2 = A(3)
print(id(a1), id(a2))
3.使用類
思路就是,調(diào)用類的instance方法,這樣有一個弊端就是在使用類創(chuàng)建的時候,并不是單例了.也就是說在創(chuàng)建類的時候一定要用類里面規(guī)定的方法創(chuàng)建
# encoding:utf-8
__author__ = 'Fioman'
__time__ = '2019/3/6 11:06'
class Singleton(object):
def __init__(self,*args,**kwargs):
pass
@classmethod
def get_instance(cls, *args, **kwargs):
# 利用反射,看看這個類有沒有_instance屬性
if not hasattr(Singleton, '_instance'):
Singleton._instance = Singleton(*args, **kwargs)
return Singleton._instance
s1 = Singleton() # 使用這種方式創(chuàng)建實例的時候,并不能保證單例
s2 = Singleton.get_instance() # 只有使用這種方式創(chuàng)建的時候才可以實現(xiàn)單例
s3 = Singleton()
s4 = Singleton.get_instance()
print(id(s1), id(s2), id(s3), id(s4))
注意,這樣的單例模式在單線程下是安全的,但是如果遇到多線程,就會出現(xiàn)問題.如果遇到多個線程同時創(chuàng)建這個類的實例的時候就會出現(xiàn)問題.
# encoding:utf-8
__author__ = 'Fioman'
__time__ = '2019/3/6 11:26'
import threading
class Singleton(object):
def __init__(self, *args, **kwargs):
pass
@classmethod
def get_instance(cls, *args, **kwargs):
if not hasattr(Singleton, '_instance'):
Singleton._instance = Singleton(*args, **kwargs)
return Singleton._instance
def task(arg):
obj = Singleton.get_instance(arg)
print(obj)
for i in range(10):
t = threading.Thread(target=task, args=[i, ])
t.start()
執(zhí)行結(jié)果好像也沒有問題,那是因為執(zhí)行的速度足夠的快,如果在init()方法中有阻塞,就看到非常的明顯.
# encoding:utf-8
__author__ = 'Fioman'
__time__ = '2019/3/6 11:26'
import threading
import time
class Singleton(object):
def __init__(self, *args, **kwargs):
time.sleep(1)
pass
@classmethod
def get_instance(cls, *args, **kwargs):
if not hasattr(Singleton, '_instance'):
Singleton._instance = Singleton(*args, **kwargs)
return Singleton._instance
def task(arg):
obj = Singleton.get_instance(arg)
print(obj)
for i in range(10):
t = threading.Thread(target=task, args=[i, ])
t.start()
可以看到是創(chuàng)建了10個不同的實例對象,這是什么原因呢.因為在一個對象創(chuàng)建的過程中,另外一個對象也創(chuàng)建了.當(dāng)它判斷的時候,會先去獲取_instance屬性,因為這個時候還沒有,它就會調(diào)用init()方法.結(jié)果就是調(diào)用了10次,然后就創(chuàng)建了10個對象.
如何解決呢?
加鎖:
在哪里加鎖呢?在獲取對象屬性_instance的時候加鎖,如果已經(jīng)有人在獲取對象了,其他的人如果要獲取這個對象,就要等一哈.因為前面的那個人,可能在第一次創(chuàng)建對象.
創(chuàng)建對象的時候加鎖即可
# encoding:utf-8
__author__ = 'Fioman'
__time__ = '2019/3/6 11:38'
import time
import threading
class Singleton(object):
_instance_lock = threading.Lock()
def __init__(self,*args,**kwargs):
time.sleep(1)
@classmethod
def get_instance(cls,*args,**kwargs):
if not hasattr(Singleton,'_instance'):
with Singleton._instance_lock:
if not hasattr(Singleton,'_instance'):
Singleton._instance = Singleton(*args,**kwargs)
return Singleton._instance
def task(arg):
obj = Singleton.get_instance(arg)
print(obj)
for i in range(10):
t = threading.Thread(target=task,args=[i,])
t.start()
obj = Singleton.get_instance()
print(obj)
這種方式創(chuàng)建的單例,必須使用Singleton_get_instance()方法,如果使用Singleton()的話,得到的并不是單例.所以我們推薦使用__new__()方法來創(chuàng)建單例,這樣創(chuàng)建的單例可以使用類名()的方法進行實例化對象
4.基于__new__
方法實現(xiàn)的單例模式(推薦使用,方便)
知識點:
1> 一個對象的實例化過程是先執(zhí)行類的__new__方法
,如果我們沒有寫,默認會調(diào)用object的__new__
方法,返回一個實例化對象,然后再調(diào)用__init__方法
,對這個對象進行初始化,我們可以根據(jù)這個實現(xiàn)單例.
2> 在一個類的__new__方法中
先判斷是不是存在實例,如果存在實例,就直接返回,如果不存在實例就創(chuàng)建.
# encoding:utf-8
__author__ = 'Fioman'
__time__ = '2019/3/6 13:36'
import threading
class Singleton(object):
_instance_lock = threading.Lock()
def __init__(self, *args, **kwargs):
pass
def __new__(cls, *args, **kwargs):
if not hasattr(cls, '_instance'):
with Singleton._instance_lock:
if not hasattr(cls, '_instance'):
Singleton._instance = super().__new__(cls)
return Singleton._instance
obj1 = Singleton()
obj2 = Singleton()
print(obj1, obj2)
def task(arg):
obj = Singleton()
print(obj)
for i in range(10):
t = threading.Thread(target=task, args=[i, ])
t.start()