http://blog.csdn.net/singwhatiwanna/article/details/17056901
爲什麼需要單例模式
有時候我們需要使用一個實用類A,這個類A專門提供一些公共功能供別人調用,而本身並不會處理業務邏輯。由於類A會被許多類乃至線程調用,假設我們的程序非常龐大,在運行的過程中,會訪問這個類A100次,爲了調用類A的方法,需要先創建A的對象,A a = new A()。這種方法在對A的訪問量較少的情況下沒問題,但是像我們這種情況,就會創建100個類A的實例,這100個實例是要佔用內存的,從這種角度來說,就造成了大量不必要的開銷。而單例模式,在整個程序生命週期中,只有一個實例,這樣就不會造成不必要的內存消耗。
單例模式的設計
爲了讓整個生命週期內只有一個實例,我們可以這樣做:
- public class Singleton {
- private static Singleton sSingleton;
- private Singleton() {
- }
- public static Singleton getInstance() {
- if (sSingleton == null) {
- sSingleton = new Singleton(); // line A
- }
- return sSingleton;
- }
- }
- public class Singleton {
- private static Singleton sSingleton;
- private Singleton() {
- }
- public static Singleton getInstance() {
- synchronized (Singleton.class) {
- if (mSingleton == null) {
- sSingleton = new Singleton();
- }
- return sSingleton;
- }
- }
- }
我們需要看一下上述代碼,真的需要每次進入getInstance方法都要獲得鎖嗎?其實不是的,整個Singleton類中,對mSingleton進行訪問的地方分爲兩類:讀和寫,而且僅當mSingleton爲null的時候纔會寫,mSingleton一旦創建完畢,後面就只剩下讀操作了,再怎麼高併發也沒什麼關係了,反正mSingleton已經是現成的,直接讀就可以了,看如下采用double-check機制的改進代碼:
上述代碼近乎完美,可以滿足幾乎所有場合(採用反射和類加載器另當別論)。上述代碼的好處在於:第一次創建實例的時候會同步所有線程,以後有線程再想獲取Singleton的實例就不需要進行同步,直接返回實例即可。還有double-check的意義在於:假設現在有2個線程A和B同時進入了getInstance方法,線程A執行到line A行,線程B執行到line B行,由於B線程還沒有初始化完畢,sSingleton還是null,於是線程A通過了sSingleton==null的判斷,並且往下執行,碰巧,當線程A執行到line
C的時候,線程B初始化完畢了,然後線程B返回,注意,如果沒有double-check,這個時候線程A就執行到了line B,就會再次初始化sSingleton,這個時候Singleton實際上被new了兩次,已經不算完全意義上的單例了,而有了double-check,就會再進行一次爲null的判斷,由於B線程已經初始化了sSingleton,所以A線程就不會再次初始化sSingleton。
- public class Singleton {
- private volatile static Singleton sSingleton;
- private Singleton() {
- }
- public static Singleton getInstance() {
- if (sSingleton == null) { // line A
- synchronized (Singleton.class) { // line C
- if (sSingleton == null)
- sSingleton = new Singleton(); // line B
- }
- }
- return sSingleton;
- }
- }
double-check(DCL)在很大程度上可以滿足高併發的需要,儘管如此,它還是有一些小缺點的,問題的關鍵在於儘管得到了Singleton的正確引用,但是卻有可能訪問到其成員變量的不正確值,這聽起來有點抽象,不過沒關係,我們只是需要有個感性的認識就好,如果你真的好奇,那麼請搜索“java happen-before”。既然DCL單例模式以中彩票的概率存在一些小問題,那麼有沒有所謂的完美的解決方案呢?答案是有,在給出之前,我要說的是:單例有很多種寫法,我們不能簡單地否定其他寫法,儘管它們看起來不能很好地處理高併發情況,也許它們本來就是用於低併發情形下的。下面請看一種所謂完美應用於高併發情形下的單例寫法,靜態內部類單例模式:
- public class Singleton
- {
- private Singleton(){
- }
- private static class InstanceHolder{
- private static final Singleton instance = new Singleton();
- }
- public static Singleton getInstance(){
- return InstanceHolder.instance;
- }
- }