設計模式 4 單例模式

2021-08-11 04:24:01 字數 4339 閱讀 2370

單例模式用來確保乙個類只有乙個例項,並提供乙個全域性訪問點。在所有的設計模式中,單例模式是最簡單也是最常用的一種設計模式,它只為乙個例項提供乙個全域性物件,內次嘗試去獲取乙個類的例項的時候,保證獲取到的都是這乙個物件。下面是關於單例模式中的一些小概念:

餓漢式和懶漢式區別:

餓漢就是類一旦載入,就把單例初始化完成,保證getinstance()的時候,單例是已經存在的了;

而懶漢比較懶,只有當呼叫getinstance()的時候,才回去初始化這個單例。

另外從以下兩點再區分以下這兩種方式:

執行緒安全

餓漢式天生就是執行緒安全的,可以直接用於多執行緒而不會出現問題;

懶漢式本身是非執行緒安全的,為了實現執行緒安全有幾種寫法,需要增加鎖或者使用靜態內部類的方式等。

資源載入和效能

餓漢式在類建立的同時就例項化乙個靜態物件出來,不管之後會不會使用這個單例,都會佔據一定的記憶體,但是相應的,在第一次呼叫時速度也會更快,因為其資源已經初始化完成,

而懶漢式顧名思義,會延遲載入,在第一次使用該單例的時候才會例項化物件出來,第一次呼叫時要做初始化,如果要做的工作比較多,效能上會有些延遲,之後就和餓漢式一樣了。

public

class

singleton

public

static singleton getinstance()

return singleton;

}}

上面是第一種寫法,當我們第一次呼叫getinstance()方法的時候,因為 singleton 是 null的,所以就例項化乙個。此後,每次再獲取該例項的時候都不為 null 了,獲取到的例項就是那個靜態的 singleton 例項。

雖然上面的方法能達到獲取單個例項的目的,但是在多執行緒中仍然是有問題的。如果乙個執行緒正在嘗試建立 singleton 的例項,而這時候剛好另乙個執行緒進入了該方法。此時,因為第乙個執行緒還沒有將 singleton 建立完畢,singleton 仍然是 null,它也進入了該方法的內部。這時候執行到 renturn 的時候,可能返回兩個例項。這在某些應用場景中可能會出現嚴重的錯誤,比如嚴格限制只能有乙個類的例項的場景,或者當該類會占用很大的資源的時候。所以,為了解決這個問題,有必要對其在多執行緒環境中的使用進行一些優化。

下面是一種優化的策咯:

public

class

singleton

public sychronized static singleton getinstance()

return singleton;

}}

這樣我們在該方法上新增了sychronized關鍵字之後,就不會有兩個方法同時進入getinstance()方法內部了。

雖然上述方法能夠達到我們保證只存在乙個例項的目的,但是這對效能造成了一定的影響,而且是沒有必要的影響。這是因為,我們其實只要在每次嘗試去建立類的例項上面加鎖就可以了,沒必要在該方法上面加鎖。因為,每次嘗試去獲取類的例項的時候,不是一定要建立的。只有第一次進入之該方法時會建立,隨後的訪問都是簡單的獲取操作了。而我們在該方法上面加鎖之後,每次獲取操作也都要等待鎖。這顯然是沒必要的,因此我們還需要繼續對其進行優化。

如果程式在建立和執行時負擔不繁重,我們可以採用立即初始化的方式來建立單例。

public

class

singleton

public

static singleton getinstance()

}

使用上面的做法,我們保證了 jvm 在每次載入該類的時候都會立即初始化唯一的例項,從而保證每次在獲取的時候都只能獲取到唯一的例項。雖然導致類裝載的原因有很多種,在單例模式中大多數都是呼叫getinstance()方法,但是也不能確定有其他的方式(或者其他的靜態方法)導致類裝載,這時候初始化 instance 顯然沒有達到lazy loading的效果。

與上面的方式類似的還有使用靜態**塊來獲取類的例項的方式:

public

class

singleton

private

singleton()

public

static singleton getinstance()

}

和上面在靜態欄位中直接初始化類的例項的方式差不都,只是將初始化的**放進了乙個靜態**塊當中。這樣例項的初始化時間仍然是在類第一次被載入的時候。

與上面使用靜態立即初始化的方法類似,你也可以使用列舉的方式來獲取乙個類的例項。它不僅能避免多執行緒同步問題,而且還能防止反序列化重新建立新的物件。下面是乙個示例:

public

enum singleton

}

利用雙重檢查加鎖,首先檢查是否例項已經建立了。如果例項尚未建立,才同步。這樣只有在第一次建立的時候才會對多執行緒進行加鎖限制:

public

class

singleton

public

static singleton getinstance()

}}return singleton;

}}

這裡 volitile 關鍵字的作用是,當多執行緒例項化 singleton 之後會立即被其他執行緒看到。

public

class

singleton

private

singleton()

public

static singleton getinstance()

}

這裡定義了乙個靜態內部類singletonholder,並在類中初始化乙個靜態singleton字段instance。當呼叫了singleton.getinstance()來獲取singleton例項的時候,會用singletonholder.instance返回例項。

這種方式同樣能夠滿足多執行緒的需求,它同樣能夠保證在第一次獲取的時候將例項初始化,此後獲取的都是例項化的結果。與上面使用靜態字段不同的是,它沒有把建立例項的靜態字段或者靜態**塊放在 singleton 中,而是放在了 singletonholder 類中。在使用靜態字段或者**塊的時候,只要 singleton 被載入,就會呼叫初始化 singleton 的操作。但是,在這方式中只有當使用 singletonholder 類的時候才會執行初始化 singleton 的操作。也就是只有呼叫singleton.getinstance()方法的時候才會對 singleton 執行初始化。

想象一下,如果例項化 singleton 很消耗資源,我想讓他延遲載入;另外一方面,我不希望在 singleton 類載入時就例項化,因為我不能確保 singleton 類還可能在其他的地方被主動使用從而被載入,那麼這個時候例項化 singleton 顯然是不合適的。這個時候,這種方式相比使用靜態字段或者**塊的方式就顯得很合理。

如果將使用靜態**塊和靜態字段看作兩個不同型別的單例方式,那麼上面我們一共列出了7種不同的實現單例的方式。

雖然,我們可以通過將乙個類的構造器設定為 private 的,來避免外部直接呼叫構造器來初始化類的例項,但是使用者可以通過accessibleobject.setaccessible()方法,通過反射來呼叫私有構造器。我們可以讓該類在建立第二個例項的時候丟擲異常。

單例的序列化問題:僅僅在類上宣告implements serializable是不夠的,還需要宣告所有例項域都是瞬時 (transient) 的,並提供乙個readresolve()方法。否則,每次反序列化乙個例項時,都會建立乙個新的例項。當然,列舉單例不會有這個問題。

public

static

class

singleton

implements

serializable

private

singleton()

public

static

final singleton getinstance()

private object readresolve()

}

原理是在 objectinputstream 內部獲取例項之後,如果該例項實現了readresolve()方法,那麼它就呼叫這個方法來獲取。

設計模式4 單例模式

保證乙個類僅有乙個例項,並提供乙個訪問他的全域性訪問點。所有類都有構造方法,假如不對他進行編碼,系統會生成空的public 的構造方法,外部類就能建立這個類的物件。為了不讓其他類能new出這個類的例項,所以需要寫乙個private 的構造方法 其實即使使用private修飾,通過反射機制還是能在外部...

設計模式 4 單例模式

單例模式屬於建立型的設計模式,其特點是在於保證乙個類只會被例項化一次,可以作為全域性唯一資源提供給系統。此處通過判斷兩個例項的位址是否一致來驗證單例模式,中包含了保證多執行緒安全的單例模式實現。由於python下的懶漢單例模式實現本人覺得是不可能的,因此下面使用了double check的方式實現了...

設計模式 4 單例模式

應用最廣的模式 單例模式。1 餓漢模式,僅下面 時,效果是懶載入的,如果這個類中有其他靜態域x,對x引用會載入類,還有就是使用掃瞄型別進行反射使用也會載入類 author cheng description 餓漢式,執行緒安全 since 2020 9 7 20 26 public class si...