單例模式以及Python實現(xiàn)

單例模式

單例模式就是確保一個類只有一個實例.當(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()
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末薇溃,一起剝皮案震驚了整個濱河市蹬挺,隨后出現(xiàn)的幾起案子聂示,更是在濱河造成了極大的恐慌,老刑警劉巖棺弊,帶你破解...
    沈念sama閱讀 221,548評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件晶密,死亡現(xiàn)場離奇詭異,居然都是意外死亡模她,警方通過查閱死者的電腦和手機惹挟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來缝驳,“玉大人,你說我怎么就攤上這事归苍∮糜” “怎么了?”我有些...
    開封第一講書人閱讀 167,990評論 0 360
  • 文/不壞的土叔 我叫張陵拼弃,是天一觀的道長夏伊。 經(jīng)常有香客問我,道長吻氧,這世上最難降的妖魔是什么溺忧? 我笑而不...
    開封第一講書人閱讀 59,618評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮盯孙,結(jié)果婚禮上鲁森,老公的妹妹穿的比我還像新娘。我一直安慰自己振惰,他們只是感情好歌溉,可當(dāng)我...
    茶點故事閱讀 68,618評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般痛垛。 火紅的嫁衣襯著肌膚如雪草慧。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,246評論 1 308
  • 那天匙头,我揣著相機與錄音漫谷,去河邊找鬼。 笑死蹂析,一個胖子當(dāng)著我的面吹牛舔示,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播识窿,決...
    沈念sama閱讀 40,819評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼斩郎,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了喻频?” 一聲冷哼從身側(cè)響起缩宜,我...
    開封第一講書人閱讀 39,725評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎甥温,沒想到半個月后锻煌,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,268評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡姻蚓,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,356評論 3 340
  • 正文 我和宋清朗相戀三年宋梧,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片狰挡。...
    茶點故事閱讀 40,488評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡捂龄,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出加叁,到底是詐尸還是另有隱情倦沧,我是刑警寧澤,帶...
    沈念sama閱讀 36,181評論 5 350
  • 正文 年R本政府宣布它匕,位于F島的核電站展融,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏豫柬。R本人自食惡果不足惜告希,卻給世界環(huán)境...
    茶點故事閱讀 41,862評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望烧给。 院中可真熱鬧燕偶,春花似錦、人聲如沸础嫡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,331評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至涧尿,卻和暖如春系奉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背姑廉。 一陣腳步聲響...
    開封第一講書人閱讀 33,445評論 1 272
  • 我被黑心中介騙來泰國打工缺亮, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人桥言。 一個月前我還...
    沈念sama閱讀 48,897評論 3 376
  • 正文 我出身青樓萌踱,卻偏偏與公主長得像,于是被迫代替她去往敵國和親号阿。 傳聞我的和親對象是個殘疾皇子并鸵,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,500評論 2 359

推薦閱讀更多精彩內(nèi)容