設計模式學習-使用go實現單例模式

單例模式

定義

什麼是單例模式:保證一個類僅有一個實例,並提供一個全局訪問它的全局訪問點。

例如:在某個伺服器程式中,該伺服器的配置資訊存放在一個文件中,這些配置數據由一個單例對象統一讀取,然後服務進程中的其他對象再通過這個單例對象獲取這些配置資訊。這樣方便了讀取,同時保證了我們的配置資訊只會初始化一次。

優點

1、在單例模式中,活動的單例只有一個實例,對單例類的所有實例化得到的都是相同的一個實例。這樣就 防止其它對象對自己的實例化,確保所有的對象都訪問一個實例

2、單例模式具有一定的伸縮性,類自己來控制實例化進程,類就在改變實例化進程上有相應的伸縮性

3、提供了對唯一實例的受控訪問

4、由於在系統記憶體中只存在一個對象,因此可以節約系統資源,當需要頻繁創建和銷毀的對象時單例模式無疑可以提高系統的性能

5、允許可變數目的實例

6、避免對共享資源的多重佔用

缺點

1、不適用於變化的對象,如果同一類型的對象總是要在不同的用例場景發生變化,單例就會引起數據的錯誤,不能保存彼此的狀態

2、由於單利模式中沒有抽象層,因此單例類的擴展有很大的困難

3、單例類的職責過重,在一定程度上違背了「單一職責原則」

4、濫用單例將帶來一些負面問題,如為了節省資源將資料庫連接池對象設計為的單例類,可能會導致共享連接池對象的程式過多而出現連接池溢出;如果實例化的對象長時間不被利用,系統會認為是垃圾而被回收,這將導致對象狀態的丟失

適用範圍

我們在項目中使用單例,都是用它來表示一些全局唯一類,比如:配置資訊類、連接池類、ID生成器類。

程式碼實現

懶漢模式

懶漢模式也就是在需要的時候,才去創建實例對象。懶漢式相對於餓漢式的優勢是支援延遲載入。不過懶漢模式不是執行緒安全的,需要加鎖。

// 使用結構體代替類
type Tool struct {
	Name string
}

// 鎖對象
var lock sync.Mutex

// 建立私有變數
var instance *Tool

// 加鎖保證執行緒安全
func GetInstance() *Tool {
	lock.Lock()
	defer lock.Unlock()
	if instance == nil {
		instance = &Tool{
			Name: "我已經初始化了",
		}
	}
	return instance
}

懶漢式的缺點也很明顯,我們給getInstance()這個方法加了一把鎖,導致這個函數的並發度很低。量化一下的話,並發度是1,也就相當於串列操作了。而這個函數是在單例使用期間,一直會被調用。如果這個單例類偶爾會被用到,那這種實現方式還可以接受。但是,如果頻繁地用到,那頻繁加鎖、釋放鎖及並發度低等問題,會導致性能瓶頸,這種實現方式就不可取了。

餓漢模式

餓漢模式的實現方式比較簡單。在類載入的時候,instance靜態實例就已經創建並初始化好了,所以,instance實例的創建過程是執行緒安全的。

var cfg *config

func init() {
	cfg = &config{
		Name: "我被初始化了",
	}
}

type config struct {
	Name string
}

// NewConfig 提供獲取實例的方法
func NewConfig() *config {
	return cfg
}

這種方式就是資源提前初始化,有人會講需要的時候才去初始化,能夠避免資源的浪費,不過也有不同的看法。

1、如果初始化耗時長,那我們最好不要等到真正要用它的時候,才去執行這個耗時長的初始化過程,這會影響到系統的性能(比如,在響應客戶端介面請求的時候,做這個初始化操作,會導致此請求的響應時間變長,甚至超時)。採用餓漢式實現方式,將耗時的初始化操作,提前到程式啟動的時候完成,這樣就能避免在程式運行的時候,再去初始化導致的性能問題。

2、如果實例佔用資源多,按照fail-fast的設計原則(有問題及早暴露),那我們也希望在程式啟動時就將這個實例初始化好。如果資源不夠,就會在程式啟動的時候觸發報錯(比如Java中的 PermGen Space OOM),我們可以立即去修復。這樣也能避免在程式運行一段時間後,突然因為初始化這個實例佔用資源過多,導致系統崩潰,影響系統的可用性。

雙重檢測

餓漢式不支援延遲載入,懶漢式有性能問題,不支援高並發。這裡又引入了一種雙重檢測的方法。

在這種實現方式中,只要instance被創建之後,即便再調用getInstance()函數也不會再進入到加鎖邏輯中了。

來看下程式碼的實現

// 使用結構體代替類
type Tool struct {
	Name string
}

//鎖對象
var lock sync.Mutex

var instance *Tool

//第一次判斷不加鎖,第二次加鎖保證執行緒安全,一旦對象建立後,獲取對象就不用加鎖了。
func GetInstance() *Tool {
	if instance == nil {
		lock.Lock()
		if instance == nil {
			instance = &Tool{
				Name: "我是雙重檢測,我已經初始化了",
			}
		}
		lock.Unlock()
	}
	return instance
}

sync.Once

go 中也提供了 sync.Once 這個方法,來控制只執行一次,具體源碼參見go中sync.Once源碼解讀

// 使用結構體代替類
type Tool struct {
	Name string
}

var instance *Tool

var once sync.Once

func GetOnceInstance() *Tool {
	once.Do(func() {
		instance = &Tool{
			Name: "我sync.once初始化的,我已經初始化了",
		}
	})
	return instance
}

參考

【單例模式】//zh.wikipedia.org/wiki/單例模式
【大話設計模式】//book.douban.com/subject/2334288/
【極客時間】//time.geekbang.org/column/intro/100039001
【單例模式的優缺點和使用場景】//www.cnblogs.com/damsoft/p/6105122.html
【單例模式】//boilingfrog.github.io/2021/11/04/使用go實現單例模式/