單例模式
wiki百科: 單例模式,也叫單子模式,是一種常用的軟體設計模式。在應用這個模式時,單例物件的類必須保證只有一個例項存在。許多時候整個系統只需要擁有一個的全域性物件,這樣有利於我們協調系統整體的行為。比如在某個伺服器程式中,該伺服器的配置資訊存放在一個檔案中,這些配置資料由一個單例物件統一讀取,然後服務程式中的其他物件再通過這個單例物件獲取這些配置資訊。這種方式簡化了在複雜環境下的配置管理。
單例模式要實現的效果就是,對於應用單例模式的類,整個程式中只存在一個例項化物件
go並不是一種物件導向的語言,所以我們使用結構體來替代
有幾種方式:
-
懶漢模式
-
餓漢模式
-
雙重檢查鎖機制
下面拆分講解:
懶漢模式
- 構建一個示例結構體
type example struct {
name string
}
複製程式碼
- 設定一個私有變數作為每次要返回的單例
var instance *example
複製程式碼
- 寫一個可以獲取單例的方法
func GetExample() *example {
// 存線上程安全問題,高併發時有可能建立多個物件
if instance == nil {
instance = new(example)
}
return instance
}
複製程式碼
-
測試一下
func main() { s := GetExample() s.name = "第一次賦值單例模式" fmt.Println(s.name) s2 := GetExample() fmt.Println(s2.name) } 複製程式碼
懶漢模式存線上程安全問題,在第3步的時候,如果有多個執行緒同時呼叫了這個方法,
那麼都會檢測到instance
為nil
,就會建立多個物件,所以出現了餓漢模式...
餓漢模式
與懶漢模式類似,不再多說,直接上程式碼
// 構建一個結構體,用來例項化單例
type example2 struct {
name string
}
// 宣告一個私有變數,作為單例
var instance2 *example2
// init函式將在包初始化時執行,例項化單例
func init() {
instance2 = new(example2)
instance2.name = "初始化單例模式"
}
func GetInstance2() *example2 {
return instance2
}
func main() {
s := GetInstance2()
fmt.Println(s.name)
}
複製程式碼
餓漢模式將在包載入的時候就建立單例物件,當程式中用不到該物件時,浪費了一部分空間
和懶漢模式相比,更安全,但是會減慢程式啟動速度
雙重檢查機制
懶漢模式存線上程安全問題,一般我們使用互斥鎖來解決有可能出現的資料不一致問題
所以修改上面的GetInstance()
方法如下:
var mux Sync.Mutex
func GetInstance() *example {
mux.Lock()
defer mux.Unlock()
if instance == nil {
instance = &example{}
}
return instance
}
複製程式碼
如果這樣去做,每一次請求單例的時候,都會加鎖和減鎖,而鎖的用處只在於解決物件初始化的時候可能出現的併發問題
當物件被建立之後,加鎖就失去了意義,會拖慢速度,所以我們就引入了雙重檢查機制(Check-lock-Check
),
也叫DCL
(Double Check Lock
), 程式碼如下:
func GetInstance() *example {
if instance == nil { // 單例沒被例項化,才會加鎖
mux.Lock()
defer mux.Unlock()
if instance == nil { // 單例沒被例項化才會建立
instance = &example{}
}
}
return instance
}
複製程式碼
這樣只有當物件未初始化的時候,才會又加鎖和減鎖的操作
但是又出現了另一個問題:每一次訪問都要檢查兩次,為了解決這個問題,我們可以使用golang標準包中的方法進行原子性操作:
import "sync"
import "sync/atomic"
var initialized uint32
func GetInstance() *example {
// 一次判斷即可返回
if atomic.LoadUInt32(&initialized) == 1 {
return instance
}
mux.Lock()
defer mux.Unlock()
if initialized == 0 {
instance = &example{}
atomic.StoreUint32(&initialized, 1) // 原子裝載
}
return instance
}
複製程式碼
以上程式碼只需要經過一次判斷即可返回單例,但是golang標準包中其實給我們提供了相關的方法:
sync.Once
的Do
方法可以實現在程式執行過程中只執行一次其中的回撥,所以最終簡化的程式碼如下:
type example3 struct {
name string
}
var instance3 *example3
var once sync.Once
func GetInstance3() *example3 {
once.Do(func() {
instance3 = new(example3)
instance3.name = "第一次賦值單例"
})
return instance3
}
func main() {
e1 := GetInstance3()
fmt.Println(e1.name)
e2 := GetInstance3()
fmt.Println(e2.name)
}
複製程式碼
單例模式是開發中經常用到的設計模式,我在製作自己的web框架 silsuer/bingo 的時候 在環境變數控制、配置項控制等位置都用到了這種模式。
想把所有設計模式使用golang實現一遍,開了個新坑silsuer/golang-design-patterns, 這是第一篇,以後會陸續更新,需要請自取~
此文章的原始碼都在這個倉庫中: golang設計模式
打個廣告,推薦一下自己寫的 go web框架 bingo,求star,求PR ~