[Effective Java]第三話:使用私有構造方法或者枚舉來實現單例

三、Item 3:使用私有構造方法或者枚舉來實現單例

單例即爲只初始化一次的類,我們可以一貫性的認爲,單例代表系統唯一的系統組件,就像是window manager(窗口管理器)或者file system(文件系統)。

我們有兩種方法實現單例。但是兩種方法我們都需要在這個待實現的單例中提供一個私有的構造方法和一個共有靜態的方法來處理這個唯一的實例。在其中的一個實現中,我麼希望類的成員變量是final修飾的。

  // Singleton with public final field
    public class Elvis {
       public static final Elvis INSTANCE = new Elvis();
       private Elvis() { ... }
       public void leaveTheBuilding() { ... }
   }

在上面的sinleton中,構造方法只會在共有靜態成員初始化的時候執行唯一的一次,我們之所以不使用“public”或者“protected”來修飾該類的構造方法是因爲只有“private”修飾符才能保證(guarantees)一個獨佔的(monoelvistic)領域(universe):這樣,事實上,只有一個Elvis的實例在內存中(當Elvis這個類被實例化的時候)–不可能多或者少,只此唯一。

第二種創建singleton的方法就是,創建一個共有靜態方法:

    // Singleton with static factory
    public class Elvis {
    private static final Elvis INSTANCE = new Elvis(); private  Elvis() { ... }
    public static Elvis getInstance() { return INSTANCE; }
       public void leaveTheBuilding() { ... }
   }

每次調用Elvis.getInstance(),只會返回一個唯一的實例。

共有成員方法最爲主要的優點就是:方法本身的聲明(getInstance)就已經申明瞭這個類是單例。所以,該方法永遠返回一個實例。

爲了能夠以實用上述任何一種方法構造的單例序列化,僅僅使得該類實現serializable接口是不充分的,爲了保證在序列化的同時,該類仍舊是單例的,你必須爲所有的實例域聲明爲“transient”,並提供一個“readResolve”(該方法來自於serializable接口)方法。否則,每次方序列化一個實例的時候,一個新的實例將會創建。下面就是我們需要在之前的例子中添加的內容。


public class Elvis implements Serializable{

    private Elvis(){}

    /**
     * 實用volatile的作用(只能在JDK 1.5+中進行使用):
     *          1、該變量在線程中不會存在副本,直接從內存中取出
     *          2、該關鍵字會禁止指令衝排序優化
     */

    /**
     * 之所以要使用volatile關鍵字的原因
     *        在instance=new Elvis();這個進行實例化的時候,JVM會做出下面的幾件事情
     *              1、在堆內存中查找該類是否曾經實例化過,如果沒有,需要通過類加載器進行類的加載;
     *              2、爲實例分配內存空間;
     *              3、調用類的構造方法進行實例化;
     *              4、將實例化的對象指向分配的內存空間。
     *
     *        只要的兩件事情即爲:3和4會在JVM執行指令重拍優化的時候將指令執行的順序進行交換,這樣,如果
     *                          過線程在運行的時候後,多於一個線程中需要持有Elvis這個實例的對象,但是,
     *                          JVM執行的順序是4->3,那麼,雖然該實例已經存在分配的內存空間,但是該類
     *                          還未進行實例化,會導致NullPointException.
     */
    private volatile static Elvis instance = null;

    public static Elvis getInstance(){
        if(instance==null){
            synchronized (Elvis.class){
                if(instance==null){
                    instance=new Elvis();
                }
            }
        }
        return instance;
    }

    protected Object readResolve()
    {
        return instance;
    }
}

在JDK 1.5之後,有第三中方法來實現單例,我們同過enum(枚舉)來進行實現

public enum Elvis2 {
    INSTANCE;
}

該種方法能夠避免絕大多數的不良情況的發生,並且,這其實是實現單例最好的方法

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章