單例模式應該是日常開發中用得最多的設計模式了,它的思想就是保證在應用中乙個類的例項只能有乙個。
在程式中我們經常會遇到有類似配置檔案的需求,一般在整個應用中配置資訊應該都是需要共享同乙份的,這時可以利用單例模式,保證在程式中用到此配置類的例項時,都是同乙個例項,保證程式執行的正確。
類似於這種情形還有很多,比如資料庫,執行緒池等。針對這種共享的情形有人可能就會有疑問,那我把這些配置資訊都設定為靜態的成員變數不就得了,當然,這樣做理論上沒問題,也能保證程式中共享乙份資訊,但是靜態變數的生命週期是跟隨類的,比普通成員變數要長,所以對記憶體是很大的消耗,這也是單例模式最大的優點,能節省記憶體。
public
class
singleinstance
public
static singleinstance getinstance()
}
可以看出這種寫法比較暴力,在類載入的時候就初始化建立了乙個例項,不管你需不需要使用都給你建立好了,所以稱為餓漢式。這種寫法的缺點就是不是懶載入,就算你不需要他也建立了,所以造成了一定的記憶體浪費。這種方式是執行緒安全的,可以正常使用,但不推薦
public
class
singleinstance
public
static singleinstance getinstance()
return sinstance;
}}
這種方式是當你需要這個例項呼叫getinstance()的時候才會去建立,所以稱為懶漢式。這種寫法看起來是解決了餓漢式浪費記憶體的情況,但這種寫法是執行緒不安全的
假設有乙個執行緒呼叫了getinstance() 方法,判斷sinstance等於null之後讓出了cpu的執行權,此時另外乙個執行緒拿到了cpu的執行權,而且也進入getinstance() 方法後判斷sinstance==null也還是true,最終這兩個執行緒就會建立出兩個例項,是執行緒不安全的,在多執行緒中不可以使用。
public
class
singleinstance
public
static
synchronized singleinstance getinstance()
return sinstance;
}}
相對於前一種,在getinstance() 方法上加了乙個鎖,用來保證執行緒安全,但是這中寫法效率太低。
既然這種效率低,是由於鎖的範圍太大,那換乙個鎖的位置試試
public
class
singleinstance
public
static singleinstance getinstance()
}return sinstance;
}}
public
class
singleinstance
public
static singleinstance getinstance()
}}return sinstance;
}}
分析懶漢式3,是因為當多個執行緒都能進入同步**塊時,會建立多個物件。所以改為在進入同步**塊之後,再判斷一次,這樣即使都先後進入了同步**塊,也不會多建立例項了,這樣既解決了執行緒安全的問題,也解決了效率低的問題。這種雙重校驗的寫法應該是平時用的最多的一種。
在雙重校驗鎖方式中,還有個重要的地方做了改動,在宣告sinstance時加了volatile關鍵字,之所以加入這個關鍵字是因為sinstance = new singleinstance()這一行**,不是原子操作。
在jvm中,這一行**並不是乙個操作完成的,而是大概會分成三個步驟去執行
給sinstance分配記憶體
new操作,建立物件
將物件指向記憶體空間
其中第三步執行之後sinstance就不等於null了,由於還存在指令重排優化,可能會先執行第3步,再執行第2部,假設某個執行緒先執行了第3步,還沒執行第二步,讓出了cpu的執行權,此時另外乙個執行緒判斷sintance已經不為null,則直接返回sintance,但實際上sinstance還並沒有建立真正的物件,最終就會導致程式出錯。
總結一下這個問題的根源就是某個執行緒對sinstance的寫操作還沒完成,存在乙個中間狀態,此時讓另外乙個執行緒拿去進行了讀操作,導致出現問題。
解決這個問題的辦法就是使用volatile關鍵字,volatile會禁止指令重排,會保證在上述的三個操作執行完之後,才會讓另外乙個執行緒進行讀操作,保障sinstance不會出現中間狀態被讀而產生錯誤的情況。
public
class
singleinstance
private
static
class
singleinstanceholder
public
static singleinstance getinstance()
}
這種寫法個人感覺就比較高階了,它主要利用了內部類的載入時機以及classloader的同步機制保證單例的實現。
這種寫法看起來類似於餓漢式的寫法,但其實內部類要在外部類呼叫getinstance()方法使用到它時才會去載入,於是就變成了懶載入模式。其次classloader在載入類的時候會保證只有乙個執行緒來載入,也不會出現執行緒不安全的問題
public
enum singleinstance
這種方式寫起來就相當暴力了,使用也是直接用singleinstance.sinstance就可以。
根據列舉的特性,因為sinstance是singleinstance的乙個例項,而且只定義了乙個sinstance,sinstance也不能被轉殖,所以就保證了單例,同時因為建立列舉的過程是執行緒安全的,所以在多執行緒中使用也沒有問題
另外列舉自身已經處理了序列化的問題,不會因為反序列化和反射產生多個例項的情況(這一塊說實話還不是很了解原理,感覺也不是幾句話能說清楚的,所以等深入研究後再做補充)
總結一下,在日常開發中用的比較多的應該就是雙重校驗鎖的方式了,但是現在大牛們更推薦的是靜態內部類和列舉的方式,特別是列舉,**簡潔,還能解決反序列化和反射引起的問題
以上就是對單例設計模式的一些理解和總結,如有不對的地方歡迎批評指正
Java設計模式 單例模式
單例模式 singleton 顧名思義,就是乙個類只有乙個例項。作為物件的建立模式,單例模式確保某乙個類只有乙個例項,而且自行例項化並向整個系統提供這個例項。這個類稱為單例類。顯然單例模式的要點有三個 一是某個類只能有乙個例項 二是它必須自行建立這個例項 三是它必須自行向整個系統提供這個例項。從具體...
java設計模式 單例模式
這個模式是很有意思,而且比較簡單,但是我還是要說因為它使用的是如此的廣泛,如此的有人緣,單例就是單 一 獨苗的意思,那什麼是獨乙份呢?你的思維是獨乙份,除此之外還有什麼不能山寨的呢?我們舉個比較難複製的物件 皇帝中國的歷史上很少出現兩個皇帝並存的時期,是有,但不多,那我們就認為皇帝是個單例模式,在這...
java設計模式 單例模式
單例模式介紹 單例模式分 懶漢式單例 餓漢式單例。單例模式有一下特點 1 單例類只能有乙個例項。2 單例類必須自己自己建立自己的唯一例項。3 單例類必須給所有其他物件提供這一例項。單例模式確保某個類只有乙個例項,而且自行例項化並向整個系統提供這個例項。單例有併發問題,只有乙個例項,多個執行緒就可能同...