設計模式:單例模式
什么是單例模式
-
單例模式(Singleton Pattern)是一個比較簡單的模式,實際應用很廣泛,比如 Spring 中的
Bean實體就是一個單例物件, -
定義:確保某一個類 只有一個實體,而且自行實體化并向整個系統提供這個實體,
單例模式的優缺點
優點
-
只有一個實體,減少了記憶體的開銷,尤其是頻繁的創建和銷毀實體,
-
單例模式可以避免對資源的多重占用,例如一個寫檔案動作,由于只有一個實體存在記憶體中,避免對同一個資源檔案的同時寫操作,
-
單例模式可以在系統設定全域的訪問點,優化和共享資源訪問,例如可以設計一個單例類,負責所有資料表的映射處理,
缺點
- 單例模式一般沒有介面,很難擴展(根據環境而定),
- 單例模式與單一職責原則有沖突,一個類應該只實作一個邏輯,而不關心是否是單例的,
單例模式的實作
- 單例模式有很多的實作方式,但是各種實作的方式都有其優缺點,下面來看看各種的實作方式,
- 單例模式的實作滿足以下幾點:
- 構造方法私有,
- 有一個靜態方法獲取該類的實體,
- 該類保證只有一個實體,
懶漢式
- 懶漢式是當用到這個物件的時候才會創建,
- 懶漢式,在需要單例模式類實體時它才創建出來給你(因為很懶),
- 優點:只有用到的時候才會創建這個物件,因此節省資源,
- 簡單的實作如下:
/**
*Singleton類,單例模式類,在類加載時便會創建一個私有靜態變數instance,也就是該類的實
*例,再通過公共介面getInstance()來發布該實體
*/
public class Singleton {
private static Singleton instance;
//私有化構造方法防止外界new物件
private Singleton (){
}
//公有化靜態函式,對外暴露單例物件的介面
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
但是這種方式并不能保證這是唯一的單例,在高并發訪問下,多個執行緒同時訪問到這個單例時,還是有可能不能保證這個類就是單例的
為了保證執行緒安全,我們可以加鎖,給這個getInstance()方法加上執行緒同步鎖synchronize具體實作如下:
public class Singleton {
private static Singleton instance;
private Singleton (){
}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
但是這種方式一旦加鎖,雖然可以保證其實單例且執行緒安全的,但是在高并發訪問下性能必然是受到影響,多個執行緒都需要用到該單例時,就無法保證速度,需要同步地等待這個單例使用完回到JVM中的堆區(Heap)才可以繼續使用這個單例,效率十分的低,
還有一種是雙重檢查式,兩次判斷
Double Check Lock(DCL)方式實作單例模式
public class Singleton{
private volatile static Singleton instance;
private Singleton(){
}
public static Singleton getInstance(){
if(instance == null){
synchronized (Singleton.class){
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
我們可以看到getInstance中對instance進行了兩次判斷是否為空,第一次判斷主要是為了避免不必要的同步問題,第二次判斷則是為了在null的情況下創建實體,因為在某些情況下會出現失效問題即DCL失效問題,可以使用volatile關鍵字處理這個問題,但是同樣的,使用了volatile關鍵字也會對性能有一定的影響,但是優點在于資源利用率高,第一次執行getInstance時物件才被實體化,但是DCL也因為第一次加載時反應慢,所以在高并發情況下也會有一定的缺陷,
餓漢式
- 餓漢式和懶漢式恰巧相反,在類加載的時候就創建實體,
- 單例模式類迫不及待的想要創建實體了(因為餓了)
- 優點:還沒用到就創建,浪費資源,
- 缺點:在類加載的時候就創建,執行緒安全,
- 實作如下:
/**
*這種方式在類加載時就完成了初始化,所以類加載較慢,但是獲取物件的速度快,這種方式
*基于類加載機制,避免了多執行緒的同步問題,如果從來沒有使用過這個實體,則會造成記憶體
*的浪費,
*/
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){
}
public static Singleton getInstance() {
return instance;
}
}
匿名內部類/靜態內部類
- 利用靜態變數、靜態代碼塊、靜態方法都是在類加載的時候只加載一次的原理,
- 實作如下
public class Singleton {
private static Singleton instance;
//靜態塊在類加載時會被執行,也就創建了Singleton類實體
static{
instance = new Singleton();
}
private Singleton (){
}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
/**
*Java靜態內部類的特性是,加載的時候不會加載內部靜態類,使用的時候才會進行加載,
*第一次加載Singleton類時并不會初始化sInstance,只有第一次呼叫getInstance方法時虛
*擬機加載SingletonHolder并初始化sInstance,這樣不僅能確保執行緒安全,也能保證
*Singleton類的唯一性,所以,推薦使用靜態內部類單例模式
*/
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){
}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
列舉單例模式
/**
*默認列舉實體的創建是執行緒安全的,并且在任何情況下都是單例,
*列舉單例的有點就是簡單,缺點是可讀性不高,
*/
public enum Singleton {
//外部呼叫由原來的Singleton.getInstance變成了Singleton.INSTANCE了,
INSTANCE;
}
總結
單例模式是運用頻率很高的模式,在我們客戶端通常是沒有高并發的情況,所以選擇哪種方式并不會有太大的影響,出于效率考慮,推薦使用靜態內部類的單例模式和DCL的單例模式,
優點:
- 由于單例模式在記憶體中只有一個實體,減少記憶體開支,特別是一個物件需要頻繁創建、銷毀時,而且創建或銷毀時性能又無法優化,單例模式的優勢就十分明顯,
- 由于單例模式只生成一個實體,所以減少了系統的性能開銷,當一個物件的產生需要比較多的資源時,如讀取配置、產生其他依賴物件時,則可通過在應用啟動時直接產生一個單例物件,然后用永久駐留的方式解決,
- 單例模式可以避免對資源的多重占用,如一個檔案的操作,由于只有一個實體存在記憶體中,避免對同一個資源檔案的同時操作,
- 單例模式可以在系統設定全域的訪問點,優化和共享資源訪問,例如,可以設計一個單例類,負責所有資料表的映射處理,
缺點:
- 單例模式一般沒有介面,擴展很困難,除非修改代碼,
- 單例物件如果持有
Context,那么很容易引發記憶體泄露,此時需要注意傳遞給單例物件的Context最好是Application Context, - 不適合用于變化頻繁的物件;如果實體化的物件長時間不被利用,系統會認為該物件是垃圾而被回收,這可能會導致物件狀態的丟失;
使用場景
- 網站訪問量計數器,
- 專案中用于讀取組態檔的類,
- Spring中,每個Bean默認都是單例的,這樣便于Spring容器進行管理,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/245654.html
標籤:java
上一篇:組合與繼承
下一篇:Servlet了解
