設計模式的重要性不用多說,也是面試時常常會被問到的問題。對於設計模式,更多的則是仁者見仁智者見智,要在實際工作中不斷的積累,再進行深度的思考,才能逐漸形成的一種思維。
單例模式也叫單子模式,是常用的模式之一,在它的核心結構中只包含一個被稱為單例的特殊類,能夠保證系統執行中一個類只建立一個範例,本節我們就來介紹一下Go語言中的單例模式。
單例模式實現
Go語言實現單例模式的有四種方式,分別是懶漢式、餓漢式、雙重檢查和 sync.Once。
懶漢式就是建立物件時比較懶,先不急著建立物件,在需要載入組態檔的時候再去建立;餓漢式則是在系統初始化的時候就已經把物件建立好了,需要用的時候直接拿過來用就好了。
不管那種模式最終目的只有一個,就是只範例化一次,只允許一個範例存在。
下面就來分別介紹一下這四種實現方式:
1) 懶漢式——非執行緒安全
非執行緒安全,指的是在多執行緒下可能會建立多次物件。
//使用結構體代替類
type Tool struct {
values int
}
//建立私有變數
var instance *Tool
//獲取單例物件的方法,參照傳遞返回
func GetInstance() *Tool {
if instance == nil {
instance = new(Tool)
}
return instance
}
在非執行緒安全的基本上,利用 Sync.Mutex 進行加鎖保證執行緒安全,但由於每次呼叫該方法都進行了加鎖操作,在效能上不是很高效。
//鎖物件
var lock sync.Mutex
//加鎖保證執行緒安全
func GetInstance() *Tool {
lock.Lock()
defer lock.Unlock()
if instance == nil {
instance = new(Tool)
}
return instance
}
2) 餓漢式
直接建立好物件,不需要判斷為空,同時也是執行緒安全,唯一的缺點是在匯入包的同時會建立該物件,並持續占有在記憶體中。
Go語言餓漢式可以使用 init 函數,也可以使用全域性變數。
type cfg struct {
}
var cfg *config
func init() {
cfg = new(config)
}
// NewConfig 提供獲取範例的方法
func NewConfig() *config {
return cfg
}
type config struct {
}
//全域性變數
var cfg *config = new(config)
// NewConfig 提供獲取範例的方法
func NewConfig() *config {
return cfg
}
3) 雙重檢查
在懶漢式(執行緒安全)的基礎上再進行優化,減少加鎖的操作,保證執行緒安全的同時不影響效能。
//鎖物件
var lock sync.Mutex
//第一次判斷不加鎖,第二次加鎖保證執行緒安全,一旦物件建立後,獲取物件就不用加鎖了。
func GetInstance() *Tool {
if instance == nil {
lock.Lock()
if instance == nil {
instance = new(Tool)
}
lock.Unlock()
}
return instance
}
5) sync.Once
通過 sync.Once 來確保建立物件的方法只執行一次
var once sync.Once
func GetInstance() *Tool {
once.Do(func() {
instance = new(Tool)
})
return instance
}
sync.Once 內部本質上也是雙重檢查的方式,但在寫法上會比自己寫雙重檢查更簡潔,以下是 Once 的原始碼
func (o *Once) Do(f func()) {
//判斷是否執行過該方法,如果執行過則不執行
if atomic.LoadUint32(&o.done) == 1 {
return
}
// Slow-path.
o.m.Lock()
defer o.m.Unlock()
//進行加鎖,再做一次判斷,如果沒有執行,則進行標誌已經掃行並呼叫該方法
if o.done == 0 {
defer atomic.StoreUint32(&o.done, 1)
f()
}
}