天天看點

單例模式的實作

  單例模式就是是一個類僅可以建立一個對象,在java中實作主要有兩種方式:餓漢式和懶漢式。

  先看兩種方式共有的部分,因為是單例,是以構造方法必須是私有的private,而且必須提供一個對外界開放的擷取對象的方法,該方法内部控制傳回唯一的一個對象執行個體:

public class Singleton {

    //構造方法私有,阻斷外界直接建立對象的方法
    private Singleton() {}
    
    //提供一個擷取唯一對象執行個體的方法
    public static Singleton getInstance(){
        //...
    }
    
}      

  以上是不管什麼方式的實作,都得遵循的一些規定,下面就介紹懶漢式和餓漢式:

  餓漢式是單例類感覺自己很饑餓(将執行個體對象想象為吃的),不管有沒有别的類跟我要執行個體類,我都要自己先生成一個,像下面的實作: 

public class Singleton {

    //構造方法私有,阻斷外界直接建立對象的方法
    private Singleton() {}
    
    //内部持有一個類初始化時唯一建立的一個類執行個體對象
    private static Singleton singleton = new Singleton();
    
    //提供一個擷取唯一對象執行個體的方法
    public static Singleton getInstance(){
        return singleton;
    }
    
}      

  餓漢式的有點在于編碼邏輯簡單好了解,無線程安全問題;缺點嘛,自然就是當我僅僅是想用Singleton類其他方法,他還是建立了一個對我現在來說沒用的執行個體對象,當系統中這種餓漢式的單例類多起來的時候,無疑是一種資源浪費,這個問題正好懶漢式可以解決。

  懶漢式,就是自己首先持有一個空的單例類的執行個體,但是不會類一加載就建立執行個體,隻有當别人第一次要我的執行個體對象我才給他建立,懶嘛,要是沒人要我就不建立了:

public class Singleton {

    //構造方法私有,阻斷外界直接建立對象的方法
    private Singleton() {}
    
    //内部持有一個單例類的引用
    private static Singleton singleton = null;
    
    //提供一個擷取唯一對象執行個體的方法
    public static Singleton getInstance(){
        if(singleton==null){
            singleton = new Singleton();
        }
        return singleton;
    }
    
}      

  餓漢式的有點不用說了,就是解決了懶漢式的缺點;但是他的缺點就是多線程環境下,不盡人意啊,比如兩個線程同時都是第一次去擷取Singleton類的執行個體的時候,又同時執行到if(singleton==null)這一行,兩個線程就會同時進入if語句執行體中去,可能先後執行了singleton = new Singleton();這就違反了單例模式的概念,是以還得想個辦法解決這個問題。

  線程安全的餓漢式:(其實隻要将if語句上加上線程鎖,就可以避免兩個線程一起跑到這個地方來了)

public class Singleton {

    //構造方法私有,阻斷外界直接建立對象的方法
    private Singleton() {}
    
    //内部持有一個單例類的引用
    private static Singleton singleton = null;
    
    //提供一個擷取唯一對象執行個體的方法
    public static Singleton getInstance(){
        synchronized (Singleton.class) {
            if(singleton==null){
                singleton = new Singleton();
            }
        }
        return singleton;
    }
    
}      

  踏哒~,解決了,但是!你發現了麼?這樣的話,每次别人想擷取sinleton執行個體的時候都得等待别的線程釋放鎖,自己再加鎖,自己再釋放鎖,而這些鎖的操作又是那麼消耗時間,能不能再優化一下。想一想,是不是第一次通路完了,隻要sington對象執行個體,不為空,直接傳回就是了,沒有現成問題啊,應該這樣:

public class Singleton {

    //構造方法私有,阻斷外界直接建立對象的方法
    private Singleton() {}
    
    //内部持有一個單例類的引用
    private static Singleton singleton = null;
    
    //提供一個擷取唯一對象執行個體的方法
    public static Singleton getInstance(){
        if(singleton==null){
            synchronized (Singleton.class) {
                if(singleton==null){
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
    
}      

  完美!線程安全的懶漢式,既解決了資源浪費問題,又兼顧了線程安全問題。

  不過還有個更巧妙地方法,這個就涉及到内部類的隻是,當一個類加載的時候,其内部類并不加載,而是隻要第一次用到内部類的時候内部類才會加載,而且如果這個内部類是static内部類,也就是說加載了這個類一次,以後就直接擷取他就可以了,詳細參見另一篇部落格:http://www.cnblogs.com/WreckBear/p/5812942.html。

public class Singleton {

    //構造方法私有,阻斷外界直接建立對象的方法
    private Singleton() {}
    
    /*
     * 搭建一個内部靜态類,外部類加載的時候,内部類并不會加載,
     * 隻有當内部類被通路的時候才會被加載、初始化,加載之後就會一直儲存在記憶體中
     */
    public static class Get{
        public static Singleton singleton = new Singleton();
    }
    
}      

  在Main中擷取:

public class Main {

    public static void main(String[] args) {
        Singleton singleton = Singleton.Get.singleton;
    }
}      

  這種方法顯得更加優雅一點,至此,結束!