1. 程式人生 > >【設計模式】單例模式的絕對單例及執行緒安全

【設計模式】單例模式的絕對單例及執行緒安全

1. 餓漢模式

/**
 * 餓漢模式
 * @author 鄭海鵬
 * @since 2015年7月6日
 */
public class Singleton {
    private static Singleton instance = new Singleton();

    private Singleton() {
    }

    public static Singleton getInstance() {
        return instance;
    }

    public static void func() {
        System.out.println("this is a function!"
); } }

優點

執行緒安全、絕對單例

缺點:

  1. 在多例項或者有其他靜態方法時,instance在沒有使用它的時候就已經載入好了。
    例如呼叫Singleton.func(),這個方法中並沒有用到instance,但是instance也被初始化了。若該物件很大,則在沒使用時就載入很浪費記憶體。
  2. 不能防止反序列化、反射產生新的例項。

2. 懶漢模式,延時載入

/**
 * 懶漢模式
 * @author 鄭海鵬
 * @since 2015年7月6日
 */
public class Singleton {
    private static
Singleton instance = null; private Singleton() { } public static Singleton getInstance() { if (null == instance) { // ① instance = new Singleton(); } return instance; } }

優點:

instance在沒有使用時不用載入,節省記憶體。

缺點:

  1. 執行緒不安全;
    執行緒不安全原因:當多個執行緒執行到①時,都是發現instance為null,於是都去初始化了。
  2. 不能防止反序列化、反射產生新的例項。

3. 方法鎖

/**
* 方法鎖
* @author 鄭海鵬
* @since 2015年7月6日
*/
public class Singleton {
    private static Singleton instance = null;

    private Singleton() {
    }

    public static synchronized Singleton getInstance() {
        if (null == instance) {
            instance = new Singleton();
        }
        return instance;
    }

}

優點:

執行緒安全,絕對單例

缺點:

  1. 加鎖導致程式併發性降低;
  2. 不能防止反序列化、反射產生新的例項。

4. 雙重檢查鎖 DCL

/**
* 雙重檢查鎖DCL
* @author 鄭海鵬
* @since 2015年7月6日
*/
public class Singleton {
    private static Singleton instance = null;

    private Singleton() {
    }

    public static Singleton getInstance() {

        if (null == instance) { // ①
            synchronized (Singleton.class) {
                if (null == instance) { // ②
                    instance = new Singleton(); // ③
                }
            }
        }
        return instance;
    }

}

優點:

大部分時候執行緒安全,相比方法鎖程式併發性較高。

缺點:

  1. 執行緒並非絕對安全;
  2. 不能防止反序列化、反射產生新的例項。

執行緒不安全原因:

Java的亂序執行、初始化物件需要時間。
對於語句③,JVM在執行時大致做了下述三件事:
a. 在記憶體中分配一塊記憶體
b. 呼叫構造方法
c. 將記憶體的地址指向instance變數。(執行這一步後,instance != null)

如果按照abc的順序執行也不會有什麼問題。但由於Java亂序執行的機制,有可能在真實情況下執行順序為acb。
假設t1、t2是兩個執行緒。t1執行到①時,發現為null,於是執行到語句③,先執行a,再執行c,在還沒有執行b時,時間片給了t2。這時,由於instance已經分配了地址空間,instance不為null了。所以t2在執行到語句①後直接return instance,獲得了這個還沒有被初始化的物件,然後在使用時就報錯了。

也就是說,在使用DCL時,有可能得到了例項的正確引用,但卻訪問到其成員變數的不正確值。

5. 雙重檢查鎖DCL與volatile的聯用

/**
* 雙重檢查鎖DCL與volatile的聯用
* @author 鄭海鵬
* @since 2015年7月6日
*/
public class Singleton {
    private static volatile Singleton instance = null;

    private Singleton() {
    }

    public static Singleton getInstance() {

        if (null == instance) {
            synchronized (Singleton.class) {
                if (null == instance) {
                    instance = new Singleton(); // ③
                }
            }
        }
        return instance;
    }

}

優點:

執行緒安全、絕對單例

缺點:


  1. 程式碼看起來有點亂;
  2. 不能防止反序列化、反射產生新的例項
    執行緒安全的原因:

我納悶這個很久,後來發現我只關注了volatitle的一個特性:對變數的讀取每次都是從主存中讀取。volatitle還有另外一個特性:volatile遮蔽指令重排序。也就是說,執行語句③時,肯定是按照abc的順序執行的(參見四)。

6. 靜態內部類 Initialization On Demand Holder

/**
* 靜態內部類 Initialization On Demand Holder
* @author 鄭海鵬
* @since 2015年7月6日
*/
public class Singleton {

    private static class SingletonHolder{
        static final Singleton instance = new Singleton();
    }

    private Singleton() {
    }

    public static Singleton getInstance() {
        return SingletonHolder.instance;
    }

}

優點:

執行緒安全,絕對單例

缺點:

不能防止反序列化、反射產生新的例項
和餓漢模式的區別:餓漢模式會在沒有使用它的時候就已經載入例項。
執行緒安全的原因:Java的執行機制保證了static修飾的類、物件僅被初始化一次。

7. 列舉類

public enum Singleton {
    instance {
        String name = "this is name";

        public void setName(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }
    };

    public abstract void setName(String name);
    public abstract String getName();
}

優點:

執行緒安全、絕對單例、可以防止因為反序列化、反射產生新的例項

缺點:

和餓漢模式類似,會造成記憶體浪費。