前段時間,有朋友在個人讀者羣裏問了幾個關於單例模式的問題。python
爲了回答他的問題,我整理了單例模式的知識點,正好我也在寫設計模式的系列。設計模式
上一篇是講「策略模式」,若你還未閱讀,能夠點擊這裏查看:安全
本篇作爲「設計模式系列」的第二篇,來一塊兒看看「單例模式」。分佈式
以前在另外一篇公衆號文章看到一個挺搞笑的例子:函數
大意是講,老婆在中國其實就是一個很形象的單例,你要娶一個老婆須要去民政局註冊登記(要對類進行實例化),當你想再娶一個老婆時,這時民政局會說,不行,你已經有一個老婆了,而且它還會告訴你的老婆是誰。 而後有個朋友,還很生趣地評論說post
單例模式 容許你討無數個老婆,但最終你會發現你討來的老婆都是同一我的測試
玩笑以後,再回到咱們的話題,先舉幾類咱們常常見到的例子:網站
一、你們在解釋單例模式時,常常要提到的一個例子是 Windows 的任務管理器。若是咱們打開多個任務管理器窗口。顯示的內容徹底一致,若是在內部是兩個如出一轍的對象,那就是重複對象,就形成了內存的浪費;相反,若是兩個窗口的內容不一致,那就會至少有一個窗口展現的內容是錯誤的,會給用戶形成誤解,到底哪一個纔是當前真實的狀態呢?spa
二、一個項目中多個地方須要讀取同一份配置文件,若是每次使用都是導入從新建立實例,讀取文件,用完後再銷燬,這樣作的話,就形成沒必要要的IO浪費,可使用單例模式只生成一份配置在內存中。
三、還有一個常見的例子是,一個網站的訪問量、在線人數,在項目中是全局惟一(不考慮分佈式),在這種狀況下,使用單例模式是一種很好的方式。
從上面看來,在系統中確保某個對象的惟一性即一個類只能有一個實例有時是很是重要的。
按照慣例,咱們先來用代碼實踐一下,看看如何用 Python 寫單例模式。
這裏介紹了三個較爲經常使用的。
class User:
_instance = None
def __new__(cls, *args, **kwargs):
print('===== 1 ====')
if not cls._instance:
print("===== 2 ====")
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self, name):
print('===== 3 ====')
self.name = name
複製代碼
驗證結果
instances = {}
def singleton(cls):
def get_instance(*args, **kw):
cls_name = cls.__name__
print('===== 1 ====')
if not cls_name in instances:
print('===== 2 ====')
instance = cls(*args, **kw)
instances[cls_name] = instance
return instances[cls_name]
return get_instance
@singleton
class User:
_instance = None
def __init__(self, name):
print('===== 3 ====')
self.name = name
複製代碼
驗證結果
class MetaSingleton(type):
def __call__(cls, *args, **kwargs):
print("cls:{}".format(cls.__name__))
print("====1====")
if not hasattr(cls, "_instance"):
print("====2====")
cls._instance = type.__call__(cls, *args, **kwargs)
return cls._instance
class User(metaclass=MetaSingleton):
def __init__(self, *args, **kw):
print("====3====")
for k,v in kw:
setattr(self, k, v)
複製代碼
驗證結果
以上的代碼,通常狀況下沒有問題,但在併發場景中,就會出現線程安全的問題。
以下這段代碼我開啓10個線程來模擬
import time
import threading
class User:
_instance = None
def __new__(cls, *args, **kwargs):
if not cls._instance:
time.sleep(1)
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self, name):
self.name = name
def task():
u = User("wangbm")
print(u)
for i in range(10):
t = threading.Thread(target=task)
t.start()
複製代碼
從結果來觀察,很容易就發現,單例模式失效了,在10個線程下,併發建立實例,並不能保證一個類只有一個實例。
<__main__.User object at 0x1050563c8>
<__main__.User object at 0x10551a208>
<__main__.User object at 0x1050563c8>
<__main__.User object at 0x1055a93c8>
<__main__.User object at 0x1050563c8>
<__main__.User object at 0x105527160>
<__main__.User object at 0x1055f4e48>
<__main__.User object at 0x1055e6c88>
<__main__.User object at 0x1055afcf8>
<__main__.User object at 0x105605940>
複製代碼
這在 Java 中,是可使用餓漢模式來避免這個問題,在 Python 中我想到的辦法是加鎖。
首先實現一個給函數加鎖的裝飾器
import threading
def synchronized(func):
func.__lock__ = threading.Lock()
def lock_func(*args, **kwargs):
with func.__lock__:
return func(*args, **kwargs)
return lock_func
複製代碼
而後在實例化對象的函數上,使用這個裝飾函數。
import time
import threading
class User:
_instance = None
@synchronized
def __new__(cls, *args, **kwargs):
if not cls._instance:
time.sleep(1)
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self, name):
self.name = name
def task():
u = User("wangbm")
print(u)
for i in range(10):
t = threading.Thread(target=task)
t.start()
複製代碼
結果以下,如預期只生成了一個實例。
<__main__.User object at 0x10ff503c8>
<__main__.User object at 0x10ff503c8>
<__main__.User object at 0x10ff503c8>
<__main__.User object at 0x10ff503c8>
<__main__.User object at 0x10ff503c8>
<__main__.User object at 0x10ff503c8>
<__main__.User object at 0x10ff503c8>
<__main__.User object at 0x10ff503c8>
<__main__.User object at 0x10ff503c8>
<__main__.User object at 0x10ff503c8>
複製代碼
學會寫只是第一步,還有一點,至關重要,要知道爲什麼會有這個設計模式,它有什麼優點,有什麼侷限性?
總結一下,單例模式有以下優勢:
和其餘設計模式同樣,單例模式有必定的適用場景,但同時它也會給咱們帶來一些問題。
參考文章