設計模式學習-使用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
}

參考

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM