wiki百科: 單例模式,也叫單子模式,是一種經常使用的軟件設計模式。在應用這個模式時,單例對象的類必須保證只有一個實例存在。許多時候整個系統只須要擁有一個的全局對象,這樣有利於咱們協調系統總體的行爲。好比在某個服務器程序中,該服務器的配置信息存放在一個文件中,這些配置數據由一個單例對象統一讀取,而後服務進程中的其餘對象再經過這個單例對象獲取這些配置信息。這種方式簡化了在複雜環境下的配置管理。git
單例模式要實現的效果就是,對於應用單例模式的類,整個程序中只存在一個實例化對象github
go並非一種面向對象的語言,因此咱們使用結構體來替代golang
有幾種方式:設計模式
懶漢模式安全
餓漢模式服務器
雙重檢查鎖機制併發
下面拆分講解:函數
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) }