單例模式
定義
什么是單例模式:保證一個類僅有一個實例,並提供一個全局訪問它的全局訪問點。
例如:在某個服務器程序中,該服務器的配置信息存放在一個文件中,這些配置數據由一個單例對象統一讀取,然后服務進程中的其他對象再通過這個單例對象獲取這些配置信息。這樣方便了讀取,同時保證了我們的配置信息只會初始化一次。
優點
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實現單例模式/