單例模式的五種寫法

餓漢式

爲什麼叫做餓漢式,意思是很飢餓,那麼就會一開始就準備好,以防以後吃不飽,名字由此而來。代碼以下java

class HUNGRYMAN{
    //這裏實例化方法要設置成私有的,以防外部直接new對象,破壞單例
    private HUNGRYMAN(){
    }
    //這裏即爲一開始就建立好對象,須要調用的時候,直接返回,不須要新建立
    private static HUNGRYMAN INSTANCE = new HUNGRYMAN();
    //調用方法返回實例對象
    public static HUNGRYMAN getInstance(){
        return INSTANCE;
    }
}
class Main{
    public static void main(String[] args) {
    //咱們在這裏採用哈希值的方式來判斷是否相等
        HUNGRYMAN instance1 = HUNGRYMAN.getInstance();
        HUNGRYMAN instance2 = HUNGRYMAN.getInstance();
        System.out.println("instance1的哈希值爲" + instance1.hashCode() + " " + "instance2的哈希值爲" + instance2.hashCode());
        System.out.println("二者是否相等:" + (instance1 == instance2));
    }
}
//輸出:instance1的哈希值爲366712642 instance2的哈希值爲366712642
//二者是否相等:true
  • 好處:顯而易見,咱們在一開始就建立好了對象,就不會涉及到線程安全的問題(線程安全是在併發建立對象的時候纔會發生,可是這裏咱們在類生成的時候就建立好了對象,因此不會發生線程安全的問題)。
  • 壞處:壞處也是顯而易見,由於咱們在類初始化的時候就建立好了類對象,因此極可能會出現這種狀況:咱們須要使用單例的時間其實不長,可是這個類存活的時間很長,假如這個時候類裏面有一些別的對象,好比很長的字節數組,那麼就會一直佔用資源。

因爲上述的狀況,誕生了懶漢式:數組

懶漢式

與餓漢式相對,懶漢式只有在須要使用的時候纔會建立,也就是說能夠實現「延時建立」,代碼以下:安全

class LAZYMAN{
    //一樣,這裏的實例方法也須要設置成私有的,防止其餘類破壞單例
    private LAZYMAN(){
    }
    //這裏定義一個LAZYMAN類型的變量instance,可是並無建立對象
    private static LAZYMAN INSTANCE;
    //在執行getInstance方法的時候纔會建立對象
    public static LAZYMAN getInstance(){
        //判空時初始化,不然直接返回對象
        if(INSTANCE == null){
            INSTANCE = new LAZYMAN();
        }
        return INSTANCE;
    }
}
class Main{
    public static void main(String[] args) {
        LAZYMAN instance1 = LAZYMAN.getInstance();
        LAZYMAN instance2 = LAZYMAN.getInstance();
        System.out.println("instance1的哈希值爲" + instance1.hashCode() + " " + "instance2的哈希值爲" + instance2.hashCode());
        System.out.println("二者是否相等:" + (instance1 == instance2));
    }
}
//輸出:instance1的哈希值爲366712642 instance2的哈希值爲366712642
//輸出:二者是否相等:true
  • 好處:實現了延遲加載,這裏不須要考慮資源消耗的問題,也就是說,只有當執行 getInstance() 方法的時候纔會建立對象,避免了資源的消耗。
  • 壞處:線程不安全,由於咱們在併發的執行 getInstance() 的時候並無考慮線程線程安全的問題,這勢必會致使出現問題。

由此產生了雙重校驗鎖(DCL懶漢式)的單例模式實現方式併發

DCL懶漢式

線程安全的懶漢模式,直接來看代碼:線程

class LAZYMAN{
    private LAZYMAN(){
    }
    //這裏加入volatile,是爲了防止實例對象建立的時候出現指令重排
    /*
    對象建立過程(非原子性):1.分配內存空間 2.執行構造方法,初始化對象 
    3.將對象指向這個內存空間。這裏若是沒有使用volatile修飾的話,會致使
    執行順序不是123,多是132,致使對象建立尚未徹底建立完畢,可是外部
    執行到if(instance == null)這裏的時候,已經不爲空了,會直接返回,致使
    返回版初始化狀態的實例,發生錯誤。
    */
    private static volatile LAZYMAN INSTANCE;
    //在執行getInstance方法的時候纔會建立對象
    public static LAZYMAN getInstance(){
        //若是對象沒有建立過,先把整個class鎖住,等到第一個拿到鎖的線程釋放以後其餘的線程在繼續執行
        if(INSTANCE == null){
            synchronized(LAZYMAN.class){
                if(INSTANCE == null){
                    INSTANCE = new LAZYMAN();
                }
            }
        }
        return INSTANCE;
    }
}
class Main{
    public static void main(String[] args) {
        LAZYMAN instance1 = LAZYMAN.getInstance();
        LAZYMAN instance2 = LAZYMAN.getInstance();
        System.out.println("instance1的哈希值爲" + instance1.hashCode() + " " + "instance2的哈希值爲" + instance2.hashCode());
        System.out.println("二者是否相等:" + (instance1 == instance2));
    }
}
  • 好處:線程安全,看起來是十分完美的方法,又有延遲加載的功能,又線程安全
  • 壞處:咱們知道,有個東西叫作反射,能夠直接獲取到類對象,能夠操做私有變量的值,能夠直接在外部建立實例對象,因此說會破壞單例模式,也就是說,這種方法也不是絕對安全的。

關於 volatile 和 synchronized ,在另外的文章中會探討。
這裏說一下如何經過反射來破壞:code

class Main{
    public static void main(String[] args) throws Exception {
        LAZYMAN instance1 = LAZYMAN.getInstance();
        Constructor<LAZYMAN> constructor = LAZYMAN.class.getDeclaredConstructor();
        //無視私有構造器
        constructor.setAccessible(true);
        //直接建立
        LAZYMAN instance2 = constructor.newInstance();
        System.out.println("instance1的哈希值爲" + instance1.hashCode() + " " + "instance2的哈希值爲" + instance2.hashCode());
        System.out.println("二者是否相等:" + (instance1 == instance2));
    }
}
//輸出:instance1的哈希值爲366712642 instance2的哈希值爲1829164700
//二者是否相等:false

藉助內部類

class INNERSINGLE{
    private INNERSINGLE(){
    }
    //內部類,調用的時候才進行加載
    private static class SINGLEINNNER{
       private static INNERSINGLE instance = new INNERSINGLE();
    }
    //調用方法:getInstance()
    public static INNERSINGLE getInstance(){
      return SINGLEINNNER.instance;
    }

}
class Main{
    public static void main(String[] args) throws Exception {
        INNERSINGLE instance1 = INNERSINGLE.getInstance();
        INNERSINGLE instance2 = INNERSINGLE.getInstance();
        System.out.println("instance1的哈希值爲" + instance1.hashCode() + " " + "instance2的哈希值爲" + instance2.hashCode());
        System.out.println("二者是否相等:" + (instance1 == instance2));
    }
}
//輸出:instance1的哈希值爲366712642 instance2的哈希值爲366712642
//二者是否相等:true
  • 優勢:內部類與外部類不在同一時間加載,只在調用 getInstance() 方法的時候纔會去初始化INSTANCE,故而節省了內存空間。能夠認爲是餓漢式的改進版。因此會保證線程安全,同時也作到了延遲加載。

枚舉

枚舉類默認是單例模式的對象

public enum ENUMSINGLE{
    INSTANCE;
    public ENUMSINGLE getInstance(){
        return INSTANCE;
    }
}
class Main{
    public static void main(String[] args) throws Exception {
        ENUMSINGLE instance1 = ENUMSINGLE.INSTANCE;
        ENUMSINGLE instance2 = ENUMSINGLE.INSTANCE;
        System.out.println("instance1的哈希值爲" + instance1.hashCode() + " " + "instance2的哈希值爲" + instance2.hashCode());
        System.out.println("二者是否相等:" + (instance1 == instance2));
    }
相關文章
相關標籤/搜索