天天看點

go解鎖設計模式之單例模式(有個小坑要注意)

前言

哈喽,大家好,我是asong,這是我的第16篇原創文章,感謝各位的關注。今天給大家分享設計模式之單例模式,并使用go語言實作。熟悉java的同學對單例模式一定不陌生,單例模式,是一種很常見的軟體設計模式,在他的核心結構中隻包含一個被稱為單例的特殊類。通過單例模式可以保證系統中一個類隻有一個執行個體且該執行個體易于外界通路,進而友善對執行個體個數的控制并節約系統資源。下面我們就一起來看一看怎麼使用go實作單例模式,這裡有一個小坑,一定要注意一下,結尾告訴你哦~~~

什麼是單例模式

單例模式確定某一個類隻有一個執行個體。為什麼要確定一個類隻有一個執行個體?有什麼時候才需要用到單例模式呢?聽起來一個類隻有一個執行個體好像沒什麼用呢! 那我們來舉個例子。比如我們的APP中有一個類用來儲存運作時全局的一些狀态資訊,如果這個類實作不是單例的,那麼App裡面的元件能夠随意的生成多個類用來儲存自己的狀态,等于大家各玩各的,那這個全局的狀态資訊就成了笑話了。而如果把這個類實作成單例的,那麼不管App的哪個元件擷取到的都是同一個對象(比如Application類,除了多程序的情況下)。

go解鎖設計模式之單例模式(有個小坑要注意)

餓漢模式

這裡我們使用三種方式實作餓漢模式。先說一下什麼是懶漢模式吧,從懶漢這兩個字,我們就能知道,這個人很懶,是以他不可能在未使用執行個體時就建立了對象,他肯定會在使用時才會建立執行個體,這個好處的就在于,隻有在使用的時候才會建立該執行個體。下面我們一起來看看他的實作:

  • 不加鎖
package one

type singleton struct {

}

var  instance *singleton
func GetInstance() *singleton {
    if instance == nil{
        instance = new(singleton)
    }
    return instance
}      

這種方法是會存線上程安全問題的,在高并發的時候會有多個線程同時掉這個方法,那麼都會檢測​

​instance​

​為nil,這樣就會導緻建立多個對象,是以這種方法是不推薦的,我再來看第二種寫法。

  • 整個方法加鎖
type singleton struct {

}

var instance *singleton
var lock sync.Mutex

func GetInstance() *singleton {
    lock.Lock()
    defer lock.Unlock()
    if instance == nil{
        instance = new(singleton)
    }
    return instance
}      

這裡對整個方法進行了加鎖,這種可以解決并發安全的問題,但是效率就會降下來,每一個對象建立時都是進行加鎖解鎖,這樣就拖慢了速度,是以不推薦這種寫法。

  • 建立方法時進行鎖定
type singleton struct {

}

var instance *singleton
var lock sync.Mutex

func GetInstance() *singleton {
    if instance == nil{
        lock.Lock()
        instance = new(singleton)
        lock.Unlock()
    }
    return instance
}      

這種方法也是線程不安全的,雖然我們加了鎖,多個線程同樣會導緻建立多個執行個體,是以這種方式也不是推薦的。是以就有了下面的雙重檢索機制

  • 雙重檢鎖
type singleton struct {
    
}

var instance *singleton
var lock sync.Mutex

func GetInstance() *singleton {
    if instance == nil{
        lock.Lock()
        if instance == nil{
            instance = new(singleton)
        }
        lock.Unlock()
    }
    return instance
}      

這裡在上面的代碼做了改進,隻有當對象未初始化的時候,才會有加鎖和減鎖的操作。但是又出現了另一個問題:每一次通路都要檢查兩次,為了解決這個問題,我們可以使用golang标準包中的方法進行原子性操作。

  • 原子操作實作
type singleton struct {
    
}

var instance *singleton
var once sync.Once
func GetInstance() *singleton {
    once.Do(func() {
        instance = new(singleton)
    })
    return instance
}      

這裡使用了​

​sync.Once​

​​的​

​Do​

​方法可以實作在程式運作過程中隻運作一次其中的回調,這樣就可以隻建立了一個對象,這種方法是推薦的~~~。

餓漢模式

有懶漢模式,當然還要有餓漢模式啦,看了懶漢的模式,餓漢模式我們很好解釋了,因為他餓呀,是以很着急的就建立了執行個體,不用等到使用時才建立,這樣我們每次調用擷取接口将不會重新建立新的對象,而是直接傳回之前建立的對象。比較适用于:如果某個單例使用的次數少,并且建立單例消息的資源比較多,那麼就需要實作單例的按需建立,這個時候懶漢模式就是一個不錯的選擇。不過也有缺點,餓漢模式将在包加載的時候就會建立單例對象,當程式中用不到該對象時,浪費了一部分空間,但是相對于懶漢模式,不需要進行了加鎖操作,會更安全,但是會減慢啟動速度。

下面我們一起來看看go實作餓漢模式:

type singleton struct {

}

var instance = new(singleton)

func GetInstance()  *singleton{
    return instance
}

或者
type singleton struct {

}

var instance *singleton

func init()  {
    instance = new(singleton)
}

func GetInstance()  *singleton{
    return instance
}      

這兩種方法都可以,第一種我們采用建立一個全局變量的方式來實作,第二種我們使用​

​init​

​​包加載的時候建立執行個體,這裡兩個都可以,不過根據golang的執行順序,全局變量的初始化函數會比包的​

​init​

​函數先執行,沒有特别的差距。

小坑

還記得我開頭說的一句話,​

​go​

​​語言中使用單例模式有一個小坑,如果不注意,就會導緻我們的單例模式沒有用,可以觀察一下我寫的代碼,除了​

​GetInstance​

​方法外其他都使用的小寫字母開頭,知道這是為什麼嗎?

golang中根據首字母的大小寫來确定可以通路的權限。無論是方法名、常量、變量名還是結構體的名稱,如果首字母大寫,則可以被其他的包通路;如果首字母小寫,則隻能在本包中使用。可以簡單的了解成,首字母大寫是公有的,首字母小寫是私有的。這裡​

​type singleton struct {​

​​我們如果使用大寫,那麼我們寫的這些方法就沒有意義了,其他包可以通過​

​s := &singleton{}​

​建立多個執行個體,單例模式就顯得很沒有意義了,是以這裡一定要注意一下哦~~~

總結

這一篇就到此結束了,這裡講解了23種模式中最簡單的單例模式,雖然他很簡單,但是越簡單的越容易犯錯的呦,是以一定要細心對待每一件事情的呦~~

好啦,這一篇就到此結束了,我的代碼已上傳github:https://github.com/asong2020/Golang_Dream/tree/master/code_demo/singleton

歡迎star

我翻譯了一份GIN中文文檔,會定期進行維護,有需要的小夥伴背景回複[gin]即可下載下傳。

我是asong,一名普普通通的程式猿,讓我一起慢慢變強吧。我自己建了一個​

​golang​

​交流群

繼續閱讀