做者 : 夏至 歡迎轉載,也請保留這段申明
blog.csdn.net/u011418943/…java
上一篇講到策略模式,變更的代碼須要用到策略模式,感興趣的小夥伴能夠看看.
傳送門:Android 經常使用設計模式之 -- 策略模式android
單例模式的定義就不解釋過多了,相信不少小夥伴在設計的時候,都用到這個模式;經常使用的場景爲 數據庫的訪問,文件流的訪問以及網絡鏈接池的訪問等等,在這些場景中,咱們都但願實例只有一個,除了減小內存開銷以外,也防止防止多進程修改文件錯亂和數據庫鎖住的問題。
在這一篇文章中,我將帶你分析 android 常見的集中單例模式,並詳細分析他們的優缺點。讓你們在之後的選擇單例中,能夠根據實際狀況選擇。
固然,若有錯誤,也歡迎指正。
下面是介紹:數據庫
就是初始化的時候,直接初始化,典型的以時間換空間的作法。設計模式
public class SingleMode{
//構造方法私有化,這樣外界就不能訪問了
private SingleMode(){
};
//當類被初始化的時候,就直接new出來
private static SingleMode instance = new SingleMode();
//提供一個方法,給他人調用
public static SingleMode getInstance(){
return instance;
}
}複製代碼
餓漢式的好處是線程安全,由於虛擬機保證只會裝載一次,再裝載類的時候,是不會併發的,這樣就保證了線程安全的問題。
但缺點也很明顯,一初始化就實例佔內存了,但我褲子還沒脫,不想用呢。安全
爲了解決上面的問題,開了懶漢式,就是須要使用的時候,纔去加載;網絡
public class SingleMode{
//構造方法私有化,這樣外界就不能訪問了
private SingleMode(){
};
private static SingleMode mSingleMode;
public static SingleModegetInstance(){ //這裏就是延時加載的意思
if (mSingleMode == null){
mSingleMode = new SingleMode();
}
return mSingleMode;
}
}複製代碼
懶漢式如上所示,
優勢:多線程
咱們只須要在用到的時候,才申請內存,且能夠從外部獲取參數再實例化,這點是懶漢式的最大優勢了併發
缺點: 優化
單線程只實例了一次,若是是多線程了,那麼它會被屢次實例this
至於問什麼說它是線程不安全的呢?先下面這張圖:
咱們假設一下,有兩個線程,A和B都要初始化這個實例;此時 A 比較快,已經判斷 mSingleMode 爲null,正在建立實例,而 B 這時候也再判斷,但此時 A 尚未 new 完,因此 mSingleMode 仍是爲空的,因此B 也開始 new 出一個對象出來,這樣就至關於建立了兩個實例了,因此,上面這種設計並不能保證線程安全。
有人會說,簡單啊,你既然是線程併發不安全,那麼加上一個 synchronized 線程鎖不就完事了?可是這樣以來,會下降整個訪問速度,並且每次都要判斷,這個真的是咱們想要的嗎?
因爲上面的缺點,因此,咱們能夠對上面的懶漢式加個優化,如雙重檢查枷鎖:
public class SingleMode{
//構造方法私有化,這樣外界就不能訪問了
private SingleMode(){
};
private static SingleMode mSingleMode;
public static SingleMode getInstance(){
if (mSingleMode == null){
synchronized (SingleMode.class){
if (mSingleMode == null){ //二次檢測
mSingleMode = new SingleMode();
}
}
}
return mSingleMode;
}
}複製代碼
在上面的基礎上,用了二次檢查,這樣就保證了線程安全了,它會先判斷是否爲null,是纔會去加載,並且用 synchronized 修飾,則又保證了線程安全。
可是若是上面咱們沒有用 volatile 修飾,它仍是不安全的,有可能會出現null的問題。爲何?這是由於 java 在 new 一個對象的時候,它是無序的。而這個過程咱們假設一下,假若有線程A,判斷爲null了,這個時候它就進入線程鎖了 mSingleMode = new SingleMode();,它不是一蹴而就,而是須要3步來完成的。
那你可能會有疑問,這樣不是很正常嗎?怎麼會有 null 的狀況?
非也,java 虛擬機在執行上面這三步的時候,並非按照這樣的順序來的,可能會打亂,這兒就是java重排序,好比2和3調換一下:
那這個時候,mSingleMode 已經指向內存區域了,那這個時候它就不爲 null了,而實際上它並未得到構造方法,好比構造方面裏面有些參數或者方法,可是你並未獲取,然而這個時候線程B過來,而 mSingleMode已經指向內存區域不爲空了,但方法和參數並未得到, 因此,這樣你線程B在執行 mSingleMode 的某些方法時就會報錯。
固然這種狀況是很是少見的,不過仍是暴露了這種問題所在。
因此咱們用volatile 修飾,咱們都知道 volatile 的一個重要屬性是可見性,即被 volatile 修飾的對象,在不一樣線程中是能夠實時更新的,也是說線程A修改了某個被volatile修飾的值,那麼我線程B也知道它被修改了。但它還有另外一個做用就是禁止java重排序的做用,這樣咱們就不用擔憂出現上面這種null 的狀況了。以下:
public class SingleMode{
//構造方法私有化,這樣外界就不能訪問了
private SingleMode(){
};
private volatile static SingleMode mSingleMode;
public static SingleMode getInstance(){
if (mSingleMode == null){
synchronized (SingleMode.class){
if (mSingleMode == null){ //二次檢測
mSingleMode = new SingleMode();
}
}
}
return mSingleMode;
}
}複製代碼
看到這裏,是否是感受爬了幾百盤的坑,終於上了黃金段位了。。。
然而,並非,你打了排位以後發現仍是被吊打,因此咱們可能還忽略了什麼。
沒錯,這種方式,依舊存在缺點:
因爲volatile關鍵字會屏蔽會虛擬機中一些必要的代碼優化,因此運行效率並非很高。所以也建議,沒有特別的須要,不要大量使用。
筆者就遇到,使用這種模式,不知道什麼緣由,第二次進入 activity的時候,view 刷不出來,然而數據對象什麼的都存在,調得我心力交瘁,欲生欲死,最後換了其餘單例模式就ok了,但願懂的大俠告訴我一下,我只能懷疑volatile了。。。。。。
那你都這樣說了,那還怎麼玩,有沒有一種更好的方式呢?別急,往下看。
什麼叫靜態式呢?回顧一下上面的餓漢式,咱們再剛開始的就初始化了,無論你需不須要,而咱們也說過,Java 再裝載類的時候,是不會併發的,那麼,咱們能不能zuo作到懶加載,即須要的時候再去初始化,又能保證線程安全呢?固然能夠,以下:
public class SingleMode{
//構造方法私有化,這樣外界就不能訪問了
private SingleMode(){
};
public static class Holder{
private static SingleMode mSingleMode = new SingleMode();
public static SingleMode getInstance(){
return mSingleMode;
}
}
}複製代碼
除了上面的餓漢式和懶漢式,,靜態的好處在於能保證線程安全,不用去考慮太多、缺點就在於對參數的傳遞比較很差。
那麼這個時候,問題來了,參數怎麼傳遞?這個確實沒懶漢式方便,不過不要緊,咱們能夠定義一個init()就能夠了,只不過初始化的時候多了一行代碼;如:
public class SingleMode {
//構造方法私有化,這樣外界就不能訪問了
private SingleMode(){
};
public static class Holder{
private static SingleMode mSingleMode = new SingleMode();
public static SingleMode getInstance(){
return mSingleMode;
}
}
private Context mContext;
public void init(Context context){
this.mContext = context;
}
}複製代碼
初始化:
SingleMode mSingleMode = SingleMode.Holder.getInstance();
mSingleMode.init(this);複製代碼
java 1.4 以前,咱們習慣用靜態內部類的方式來實現單例模式,但在1.5以後,在 《Effective java》也提到了這個觀點,使用枚舉的優勢以下:
因此,如今通常用單個枚舉的方式來實現單例,如上面,咱們改一下:
public static SingleMode getInstance(){
return Singleton.SINGLETON.getSingleTon();
}
public enum Singleton{
SINGLETON ; //枚舉自己序列化以後返回的實例,名字隨便取
private AppUninstallModel singleton;
Singleton(){ //JVM保證只實例一次
singleton = new AppUninstallModel();
}
// 公佈對外方法
public SingleMode getSingleTon(){
return singleton;
}
}複製代碼
好吧,這樣就ok了,但仍是那個問題,初始化參數跟靜態類同樣,仍是得從新寫個 init() 有失必有得吧。
這樣,咱們的單例模式就學完了。