這篇博客我們繼續來看設計模式,今天帶來的是一個最簡單而且最常用的模式-單例模式。那什么是單例模式呢?相信大家最它最熟悉不過了,那我們就來快速的了解一下它的定義。
~~~
保證一個類僅有一個實例,并提供一個訪問它的全局訪問點。
~~~
這個解釋足夠簡單。說白了就是假如我們希望我們在我們的系統中該類僅僅存在1個或0個該類的實例。雖然單例模式很簡單,但是熟悉java的同學可能了解,單例模式有很多寫法,懶漢式、餓漢式、雙重鎖。。。 這么多形式,難道有什么目的?確實,不過他們的目的很明確,就是保證在一種特殊情況下的單例-并發。
ok,既然了解了單例模式,那下面我們就開始用代碼描述一下單例模式。首先是最簡單的單例,這里我們并不去考慮并發的情況。`
~~~
package manager
import (
"fmt"
)
var m *Manager
func GetInstance() *Manager {
if m == nil {
m = &Manager {}
}
return m
}
type Manager struct {}
func (p Manager) Manage() {
fmt.Println("manage...")
}
~~~
這就是一個最簡單的單例了,對于Manager結構體,我們提供了一個GetInstance函數去獲取它的實例,這個函數中首先去判斷m變量是否為空,如果為空才去賦值一個Manager的指針類型的值,一個小小的判斷,就保證了我們在第第二次調用GetInstance的時候直接返回m,而不是重新獲取Manager的實例,進而保證了唯一實例。
上面的代碼確實簡單,也實現了最簡單的單例模式,不過大家有沒有考慮到并發這一點,在并發的情況下,這里是不是還可以正常工作呢? 來,先跟著下面的思路走一走,來看看問題出現在哪。
~~~
現在我們是在并發的情況下去調用的 GetInstance函數,現在恰好第一個goroutine執行到m = &Manager {}這句話之前,第二個goroutine也來獲取實例了,第二個goroutine去判斷m是不是nil,因為m = &Manager{}還沒有來得及執行,所以m肯定是nil,現在出現的問題就是if中的語句可能會執行兩遍!
~~~
在上面介紹的這種情形中,因為m = &Manager{}可能會執行多次,所以我們寫的單例失效了,這個時候我們就該考慮為我們的單例加鎖啦。
這個時候我們就需要引入go的鎖機制-sync.Mutex了,修改我們的代碼,
~~~
package manager
import (
"sync"
"fmt"
)
var m *Manager
var lock *sync.Mutex = &sync.Mutex {}
func GetInstance() *Manager {
lock.Lock()
defer lock.Unlock()
if m == nil {
m = &Manager {}
}
return m
}
type Manager struct {}
func (p Manager) Manage() {
fmt.Println("manage...")
}
~~~
代碼做了簡單的修改了,引入了鎖的機制,在GetInstance函數中,每次調用我們都會上一把鎖,保證只有一個goroutine執行它,這個時候并發的問題就解決了。不過現在不管什么情況下都會上一把鎖,而且加鎖的代價是很大的,有沒有辦法繼續對我們的代碼進行進一步的優化呢? 熟悉java的同學可能早就想到了雙重的概念,沒錯,在go中我們也可以使用雙重鎖機制來提高效率。
~~~
package manager
import (
"sync"
"fmt"
)
var m *Manager
var lock *sync.Mutex = &sync.Mutex {}
func GetInstance() *Manager {
if m == nil {
lock.Lock()
defer lock.Unlock()
if m == nil {
m = &Manager {}
}
}
return m
}
type Manager struct {}
func (p Manager) Manage() {
fmt.Println("manage...")
}
~~~
代碼只是稍作修改而已,不過我們用了兩個判斷,而且我們將同步鎖放在了條件判斷之后,這樣做就避免了每次調用都加鎖,提高了代碼的執行效率。
這獲取就是很完美的單例代碼了,不過還沒完,在go中我們還有更優雅的方式去實現。單例的目的是啥?保證實例化的代碼只執行一次,在go中就中這么一種機制來保證代碼只執行一次,而且不需要我們手工去加鎖解鎖。對,就是我們的sync.Once,它有一個Do方法,在它中的函數go會只保證僅僅調用一次!再次修改我們的代碼,
~~~
package manager
import (
"sync"
"fmt"
)
var m *Manager
var once sync.Once
func GetInstance() *Manager {
once.Do(func() {
m = &Manager {}
})
return m
}
type Manager struct {}
func (p Manager) Manage() {
fmt.Println("manage...")
}
~~~
代碼更簡單了,而且有沒有發現-漂亮了!Once.Do方法的參數是一個函數,這里我們給的是一個匿名函數,在這個函數中我們做的工作很簡單,就是去賦值m變量,而且go能保證這個函數中的代碼僅僅執行一次!
ok,到現在單例模式我們就介紹完了,內容并不多,因為單例模式太簡單而且太常見了。我們用單例的目的是為了保證在整個系統中存在唯一的實例,我們加鎖的目的是為了在并發的環境中單例依舊好用。不過雖然單例簡單,我們還是不能任性的用,因為這樣做實例會一直存在內存中,一些我們用的不是那么頻繁的東西使用了單例是不是就造成了內存的浪費?大家在用單例的時候還是要多思考思考,這個模塊適不適合用單例!
原文:http://blog.csdn.net/qibin0506/article/details/50733314

- Go語言基礎篇
- Go語言簡介
- Go語言教程
- Go語言環境安裝
- Go語言結構
- Go語言基礎語法
- Go語言數據類型
- Go語言變量
- Go語言提高篇
- Go語言實現貪吃蛇
- Go 諺語
- 解決連通性問題的四種算法
- golang 幾種字符串的連接方式
- Go JSON 技巧
- Go += 包版本
- Golang 編譯成 DLL 文件
- Go指南:牛頓法開方
- Go語言異步服務器框架原理和實現
- Golang適合高并發場景的原因分析
- 如何設計并實現一個線程安全的 Map ?(上篇)
- go語言執行cmd命令關機、重啟等
- IT雜項
- IT 工程師的自我管理
- IT界不為人知的14個狗血故事
- Go語言版本說明
- Go 1.10中值得關注的幾個變化
- Golang面試題解析
- Golang面試題
- Golang語言web開發
- golang 模板(template)的常用基本語法
- go語言快速入門:template模板
- Go Template學習筆記
- LollipopGo框架
- 框架簡介
- Golang語言版本設計模式
- 設計模式-單例模式
- Golang語言資源下載
- 公眾賬號
- leaf
- 合作講師
- 公開課目錄