最近在學習Golang,想着能夠就之前的知識作一些串通,加上了解到go語言也是面向對象編程語言以後。在最近的開發過程當中,我碰到一個問題,要用go語言實現單例模式。本着「天下知識,同根同源」(我瞎掰的~),我心想,這有什麼難的,但是真正作起來,仍是碰到了很多問題。編程
下面是個人經歷:安全
1.我先是完成了個人初版單例模式,就是非併發,最簡單的一種,懶漢模式:併發
var instance *single type single struct{ Name string } func GetInstance()*single{ if m == nil{ m = &single{} } return m } func main(){ a := GetInstance() a.Name = "a" b := GetInstance() b.Name = "b" fmt.Println(&a.Name, a) fmt.Println(&b.Name, b) fmt.Printf("%p %T\n", a, a) fmt.Printf("%p %T\n", b, b) }
結果以下:編程語言
0xc04203e1b0 &{b}
0xc04203e1b0 &{b}
0xc04203e1b0 *main.single
0xc04203e1b0 *main.single函數
能夠看到,咱們已經實現了簡單的單例模式,咱們申請了兩次實例,在改變一個第二個實例的字段以後,第一個也隨之改變了。並且從他們的地址都相同也能夠看出是同一個對象。可是,這樣簡陋的單例模式在併發下就容易出錯,非線程安全的。高併發
如今咱們是在併發的狀況下去調用的 GetInstance
函數,如今剛好第一個goroutine執行到m = &Manager {}
這句話以前,第二個goroutine也來獲取實例了,第二個goroutine去判斷m是否是nil,由於m = &Manager{}
尚未來得及執行,因此m確定是nil,如今出現的問題就是if中的語句可能會執行兩遍!學習
2.緊接着咱們作了一些改進,給單例模式加了鎖:優化
var m *single var lock sync.Mutex type single struct{ Name string } func GetInstance()*single{ lock.Lock() defer lock.Unlock() if m == nil{ m = &single{} } return m }
結果同上。線程
與此同時,新的問題出現了,在高併發環境下,如今無論什麼狀況下都會上一把鎖,並且加鎖的代價是很大的,有沒有辦法繼續對咱們的代碼進行進一步的優化呢?code
3.雙重鎖機制:
var m *single var lock sync.Mutex type single struct{ Name string } func GetInstance()*single{ if m == nil{ lock.Lock() defer lock.Unlock() if m == nil{ m = &single{} } } return m }
此次咱們用了兩個判斷,並且咱們將同步鎖放在了條件判斷以後,這樣作就避免了每次調用都加鎖,提升了代碼的執行效率。理論上寫到這裏已是很完美的單例模式了,可是咱們在go語言裏,咱們有一個很優雅的寫法。
4.sync包裏的Once.Do()方法
var m *single var once sync.Once type single struct{ Name string } func GetInstance()*single{ once.Do(func() { m = &single{} }) return m }
Once.Do方法的參數是一個函數,這裏咱們給的是一個匿名函數,在這個函數中咱們作的工做很簡單,就是去賦值m變量,並且go能保證這個函數中的代碼僅僅執行一次!
之後在用go語言寫單例模式的時候,可不要再傻傻的去使用前面那些例子了,既然已經有了優雅又強大的方法,咱們直接用就完了。