GO 單例模式

 

wiki百科: 單例模式,也叫單子模式,是一種經常使用的軟件設計模式。在應用這個模式時,單例對象的類必須保證只有一個實例存在。許多時候整個系統只須要擁有一個的全局對象,這樣有利於咱們協調系統總體的行爲。好比在某個服務器程序中,該服務器的配置信息存放在一個文件中,這些配置數據由一個單例對象統一讀取,而後服務進程中的其餘對象再經過這個單例對象獲取這些配置信息。這種方式簡化了在複雜環境下的配置管理。git

單例模式要實現的效果就是,對於應用單例模式的類,整個程序中只存在一個實例化對象github

go並非一種面向對象的語言,因此咱們使用結構體來替代golang

有幾種方式:設計模式

  • 懶漢模式安全

  • 餓漢模式服務器

  • 雙重檢查鎖機制併發

下面拆分講解:函數

懶漢模式

  1. 構建一個示例結構體
   type example struct {
   	name string
   }
  1. 設置一個私有變量做爲每次要返回的單例
  var instance *example
  1. 寫一個能夠獲取單例的方法
    func GetExample() *example {
    
    	// 存在線程安全問題,高併發時有可能建立多個對象
    	if instance == nil {
    		instance = new(example)
    	}
    	return instance
    }
  1. 測試一下高併發

      func main() {
      	s := GetExample()
      	s.name = "第一次賦值單例模式"
      	fmt.Println(s.name)
      
      	s2 := GetExample()
      	fmt.Println(s2.name)
      }

懶漢模式存在線程安全問題,在第3步的時候,若是有多個線程同時調用了這個方法, 那麼都會檢測到instancenil,就會建立多個對象,因此出現了餓漢模式...測試

餓漢模式

與懶漢模式相似,再也不多說,直接上代碼

  // 構建一個結構體,用來實例化單例
  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.OnceDo方法能夠實如今程序運行過程當中只運行一次其中的回調,因此最終簡化的代碼以下:

 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)
 }
相關文章
相關標籤/搜索