簡介
單例物件的類必須保證只有乙個例項存在,並提供乙個全域性訪問點。可以減少記憶體開銷,同時避免對資源的多重占用。連線池、執行緒池都是常見的單例模式。
實現乙個單例模式基本有兩種思路,一種是在初次使用時構建(懶漢式),一種是在類載入時構建(餓漢式)。這兩種思路中,該類的建構函式都必須定義為私有方法,這樣其他處的**就無法通過呼叫該類的建構函式來例項化該類的物件,只有通過該類提供的靜態方法來得到該類的唯一例項。下面我們嘗試實現一下。
單例模式的實現
第一種思路,例項在初次呼叫時構建。當我們呼叫獲取例項的方法getinstance時,如果類持有的引用不為空就返回這個引用,如果為空就建立該類的例項,並將該例項賦予該類持有的引用。
public
class
lazysingleton
public
static lazysingleton getinstance()
return instance;
}}
然而這種方式在多執行緒場景下是不安全的。如果當唯一例項尚未建立時,有兩個執行緒同時呼叫建立方法,那麼它們同時沒有檢測到唯一例項的存在,從而同時各自建立了乙個例項,這樣就有兩個例項被構造出來,從而違反了單例模式中例項唯一的原則。我們可以使用雙重檢查鎖來避免這個問題。
public
class
doublechecklazysingleton
public
static doublechecklazysingleton getinstance()
}}return instance;
}}
public
class
doublechecklazysingleton
public
static doublechecklazysingleton getinstance()
}}return instance;
}}
public
class
staticinnerclasssingleton
private
static
class
innerclass
public
static staticinnerclasssingleton getinstance()
}
另一種思路,在類載入時構建例項。這種方案有可能會造成資源浪費,比如這個物件你自始至終就沒有使用過。但是簡單。對於類載入時即初始化的例項,我比較喜歡放在static**塊中,這樣比較一目了然。
public
class
hungrysingleton
private
hungrysingleton()
public
static hungrysingleton getinstance()
}
隱患
你以為搞定了執行緒安全問題,單例模式就沒有其他隱患了麼,too young。單例模式作為經常被cue的設計模式,還是有很多料可以挖的。想要破壞我們上面實現的單例模式,序列化可以,反射也可以。
我們用比較簡單的餓漢式作為例子,為了測試序列化破壞單例,hungrysingleton需要先實現serializable介面。然後就開始破壞吧。
public
class
test
}
執行後控制台輸出結果為:
instance: com.anne.design.pattern.creational.singleton.hungrysingleton@3764951d實驗表明,通過序列化和反序列化,我們的單例模式遭到了破壞,現在系統裡有兩個hungrysingleton例項了,違背了單例模式的初衷。哭唧唧。那麼怎麼辦呢?newinstance: com.anne.design.pattern.creational.singleton.hungrysingleton@34c45dca
instance == newinstance: false
操作很簡單,只需要在hungrysingleton類中增加readresolve()方法即可。
public
class
hungrysingleton
implements
serializable
private
hungrysingleton()
public
static hungrysingleton getinstance()
private object readresolve()
}
在執行上面的測試方法,我們就可以得到
instance: com.anne.design.pattern.creational.singleton.hungrysingleton@3764951d這是為什麼呢?readresolve()為何有如此功能?答案在objectinputstream的readobject()方法中,其中一段:newinstance: com.anne.design.pattern.creational.singleton.hungrysingleton@3764951d
instance == newinstance: true
if
(obj != null && handles.
lookupexception
(passhandle)
== null && desc.
hasreadresolvemethod()
)}return obj;
hasreadresolvemethod就是看這個物件的類中,是否有readresolve(),如果有就通過反射呼叫這個方法,並返回這個方法指定的物件。
之前說了,破壞單例模式這件事,序列化反序列化可以,反射也可以。因為我們可以利用反射改變構造方法的可見性呀。
public
class
test
}
輸出的結果為:
instance: com.anne.design.pattern.creational.singleton.hungrysingleton@2b193f2d對於這種情況,我們可以在hungrysingleton的構造方法中增加防禦機制。newinstance: com.anne.design.pattern.creational.singleton.hungrysingleton@355da254
instance == newinstance: false
public
class
hungrysingleton
implements
serializable
private
hungrysingleton()
}public
static hungrysingleton getinstance()
private object readresolve()
}
再測試,就會丟擲異常,不會出現重複建立的情況。這個方案同樣也實用於通過靜態內部類實現的懶漢式。而對於一般的懶漢式,反射破壞防不勝防,因為無論用多麼複雜的邏輯去判斷instance已經建立,我們都可以用反射修改其中的關鍵值。
總結單例模式看起來簡單,可實際實現的過程,道阻且長。不過只要注意以下幾點,就會思路清晰啦。
必須有乙個private構造器。
注意執行緒安全問題。
注意序列化和反序列化的安全問題。
注意反射攻擊。
單例設計模式 序列化破壞單例模式?
1 問題猜想,假如將乙個物件通過序列化放到乙個檔案後,再取出來看是否與本身相等?public class hungrysingleton implements serializable private hungrysingleton public static hungrysingleton get...
摩卡,讓我們一起成長
本人加入摩卡倒頗有些緣分 在剛籌畫成立天津研發中心時,就差點成為研發中心的一員,但由於種種原因未能成行 經過一年多的等待,最終還是走進了摩卡,成為一名上海本土員工。時間如梭,加入摩卡轉眼三年了,我能很清楚地感覺到自己的成長與進步,同時也目睹了公司快速健康發展與壯大的過程,能成為摩卡人的一分子感到榮幸...
摩卡,讓我們一起成長
本人加入摩卡倒頗有些緣分 在剛籌畫成立天津研發中心時,就差點成為研發中心的一員,但由於種種原因未能成行 經過一年多的等待,最終還是走進了摩卡,成為一名上海本土員工。時間如梭,加入摩卡轉眼三年了,我能很清楚地感覺到自己的成長與進步,同時也目睹了公司快速健康發展與壯大的過程,能成為摩卡人的一分子感到榮幸...