這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
單件模式比較常見,算是建立型的設計模式,和原廠模式不同,他只能建立一個執行個體。他的應用情境很多,比如MySQL只能有一個執行個體這種都算。
單件模式能簡單分成支援並發和不支援並發兩種。不過並發這個很簡單,滿大街Golang實現的單件模式都是這樣的。
普通的單件模式
package singletonimport ("fmt")var _self *Singletontype Singleton struct {Name string}func Instance() *Singleton {if _self == nil {_self = new(Singleton)return _self}return _self}func (o *Singleton) SetName(s string) {_self.Name = s}func (o *Singleton) GetName() {fmt.Println("Name:", _self.Name)}
支援並發的單件模式也不算難,在這個基礎上增加一個叫做Double Check
的處理。當年在去哪兒網面試被虐的時候被問到過這個,所以這個東西一直也都記著。
單件模式在並發情況下,上面的代碼就有問題了,有可能會被建立多次,上面的例子加個日誌:
var _self *Singletontype Singleton struct {Name string}func NewInstance(name string) *Singleton {fmt.Println("Create instance", name)time.Sleep(4 * time.Second)_self.Name = namereturn _self}func Instance(name string) *Singleton {if _self.Name == "" {return NewInstance(name)}return _self}func main() {_self = new(Singleton)go Instance("cyeam")go Instance("bryce")time.Sleep(10 * time.Second)fmt.Println(_self.Name)}
結果如下。例子裡面給執行個體化函數加了參數,方便判斷執行個體化的次數和結果。從下面的結果看來,無鎖的單件模式建立過兩次執行個體,第二次建立的執行個體覆蓋了第一個建立的執行個體。
Create instance cyeamCreate instance brycebryce
支援並發的單件模式
為了保證在並行調用的情況下只建立一個執行個體,就需要加鎖來保證串列建立。簡單粗暴的方法就是Instance()
方法直接全部上鎖。
var _self *Singletontype Singleton struct {Name stringsync.Mutex}func NewInstance(name string) *Singleton {fmt.Println("Create instance", name)time.Sleep(4 * time.Second)_self.Name = namereturn _self}func Instance(name string) *Singleton {_self.Mutex.Lock()defer _self.Mutex.Unlock()if _self.Name == "" {return NewInstance(name)}return _self}func main() {_self = new(Singleton)_self.Mutex = sync.Mutex{}go Instance("cyeam")go Instance("bryce")time.Sleep(10 * time.Second)fmt.Println(_self.Name)}
簡單粗暴的加鎖,可以發現能夠解決並發多次建立的問題。但是如此一來,整個建立流程就變成串列調用了。比如有1000次建立請求,只要建立一個執行個體就好,剩下999次完全沒有必要加鎖,直接將之前第一個建立的執行個體返回就好。而這樣的寫法會導致每一次都是帶鎖訪問,影響速度。
Create instance cyeamcyeam
接著上面的思路,我們可以不為這個函數整體加鎖,在建立的時候加鎖即可。
func Instance(name string) *Singleton {if _self.Name == "" {_self.Mutex.Lock()defer _self.Mutex.Unlock()return NewInstance(name)}return _self}
在判斷確認對象為空白後,開始建立對象,結果如下:
Create instance cyeamCreate instance brycebryce
雖然已經加上了鎖,但是可以看到,依然建立了兩次對象。如果兩個並行的建立調用,此時if _self.Name == ""
這個調用也同時執行,自然也都是true
,接著,雖然有鎖,但是也就是串列得去建立對象,外面的if
在無鎖的情況是失效了。
這時就需要牛逼的Double Check了,在鎖裡面再加一次判空檢查。
func Instance(name string) *Singleton {if _self.Name == "" {_self.Mutex.Lock()defer _self.Mutex.Unlock()if _self.Name == "" {return NewInstance(name)}}return _self}
結果如下。如此一來,既兼顧了效率,又能夠很好的支援並發。
Create instance cyeamcyeam
上面寫的這些都是傳統的寫法,對於一般語言使用的,對於我大Golang,還有一個更簡單的實現。
import ("fmt""sync""time")var _self *Singletontype Singleton struct {Name stringsync.Once}func NewInstance(name string) *Singleton {fmt.Println("Create instance", name)time.Sleep(4 * time.Second)_self.Name = namereturn _self}func Instance(name string) *Singleton {if _self.Name == "" {_self.Once.Do(func() { NewInstance(name) })}return _self}func main() {_self = new(Singleton)_self.Once = sync.Once{}go Instance("cyeam")go Instance("bryce")time.Sleep(10 * time.Second)fmt.Println(_self.Name)}
本文所涉及到的完整源碼請參考。
參考文獻
- 設計模式(2) - Singleton單件模式 - shltsh
原文連結:單件模式——Golang實現,轉載請註明來源!