Java Singleton(單例模式) 實現詳解


什么是單例模式?

 

Intend:Ensure a class only has one instance, and provide a global point of access to it.

目標:保證一個類只有一個實例,並提供全局訪問點

--------(《設計模式:可復用面向對象軟件的基礎》

就運行機制來說,就是一個類,在運行過程中只存在一份內存空間,外部的對象想使用它,都只會調用那部分內存。

其目的有實現唯一控制訪問,節約資源,共享單例實例數據。

單例基礎實現有兩種思路,

1.Eager initialization:在加載類時構造;2.Lazy Initialization:在類使用時構造;3

1.Eager initialization適用於高頻率調用,其由於預先創建好Singleton實例會在初始化時使用跟多時間,但在獲得實例時無額外開銷

其典型代碼如下:

public class EagerInitSingleton {
    //構建實例
    private static final EagerInitSingleton SINGLE_INSTANCE = new EagerInitSingleton();
    //私有化構造器
    private EagerInitSingleton(){}
    //獲得實例
    public static EagerInitSingleton getInstance(){
        return SINGLE_INSTANCE;
    }
}

換一種思路,由於類內靜態塊也只在類加載時運行一次,所以也可用它來代替構造單例:

public class EagerInitSingleton {
    //構建實例
    //private static final EagerInitSingleton instance = new EagerInitSingleton();
    //此處不構造
    private static StaticBlockSingleton instance;

    //使用靜態塊構造
    static{
        try{
            instance = new StaticBlockSingleton();
        }catch(Exception e){
            throw new RuntimeException("Exception occured in creating singleton instance");
        }
    }
    
    //私有化構造器
    private EagerInitSingleton(){}
    //獲得實例
    public static EagerInitSingleton getInstance(){
        return instance;
    }
}

 

2.Lazy Initialization適用於低頻率調用,由於只有使用時才構建Singleton實例,在調用時會有系列判斷過程所以會有額外開銷

2.1Lazy Initialization單線程版 

其初步實現如下:

public class LazyInitSingleton {

    private static LazyInitSingleton SINGLE_INSTANCE = null;

    //私有化構造器
    private LazyInitSingleton() {}

    //構造實例
    public static LazyInitSingleton getInstance() {
        if (SINGLE_INSTANCE == null) {          
          SINGLE_INSTANCE = new LazyInitSingleton();//判斷未構造后再構造
        }
        return SINGLE_INSTANCE;
    }
}

 

2.2Lazy Initialization多線程版

在多線程下,可能多個線程在較短時間內一同調用 getInstance()方法,且判斷

SINGLE_INSTANCE == null

結果都為true,則2.1Lazy Initialization單線程版 會構造多個實例,即單例模式失效

作為修正

2.2.1synchronized關鍵字第一版

可考慮使用synchronized關鍵字同步獲取方法

public class  LazyInitSingleton {

    private static  LazyInitSingleton SINGLE_INSTANCE = null;

    //私有化構造器
    private  LazyInitSingleton() {}

    //構造實例,加入synchronized關鍵字
    public static synchronized LazyInitSingleton getInstance() {
        if (SINGLE_INSTANCE == null) {          
          SINGLE_INSTANCE = new  LazyInitSingleton();//判斷未構造后再構造
        }
        return SINGLE_INSTANCE;
    }
}

2.2.2synchronized關鍵字第二版(double checked locking 二次判斷鎖)

以上可實現線程安全,但由於使用了synchronized關鍵字實現鎖定控制,getInstance()方法性能下降,造成瓶頸。分析到需求構建操作只限於未構建判斷后第一次調用getInstance()方法,即構建為低頻操作,所以完全可以在判斷已經構建后直接返回,而不需要使用鎖,僅在判斷需要構建后才進行鎖定:

public class  LazyInitSingleton {

    private static  LazyInitSingleton SINGLE_INSTANCE = null;

    //私有化構造器
    private  LazyInitSingleton() {}

    //構造實例
    public static synchronized   LazyInitSingleton getInstance() {
        if (SINGLE_INSTANCE == null) { 
            synchronized(LazyInitSingleton.class){
                if (SINGLE_INSTANCE == null) {
                    SINGLE_INSTANCE = new  LazyInitSingleton();//判斷未構造后再構造
                }
            }
        }
        return SINGLE_INSTANCE;
    }
}

 

3利用靜態內部類實現懶加載

 JVM僅在使用時加載靜態資源,當類加載時,靜態內部類不會加載,僅當靜態內部類在使用時會被加載,且實現順序初始化即加載是線程安全的,利用這一性質,我們可以實現懶加載

 public class NestedSingleton {

    private NestedSingleton() {}
    //靜態內部類,只初始化一次
    private static class SingletonClassHolder {
        static final NestedSingleton SINGLE_INSTANCE = new NestedSingleton();
    }
    //調用靜態內部類方法得到單例
    public static NestedSingleton getInstance() {
        return SingletonClassHolder.SINGLE_INSTANCE;
    }

}

 

4.使用Enum

由於枚舉類是線程安全的,可以直接使用枚舉創建單例。但考慮到枚舉無法繼承,所以只在特定情況下使用

public enum EnumSingleton {

    INSTANCE;
}

 

 

 附1 利用反射機制破解單例(非Enum形式的單例,)

單例形式的類可被反射破解,從而使用單例失效,即將其Private構造器,通過反射形式暴露出來,並進行實例的構造

    public static void main(String[] args) {
        
        NestedSingleton nestedSingleton =  NestedSingleton.getInstance();
        NestedSingleton nestedSingleton2 = null;
        try {
            //暴露構造器
            Constructor[] constructors = nestedSingleton.getClass().getDeclaredConstructors();
            Constructor constructor = constructors[1];
            constructor.setAccessible(true);
            nestedSingleton2 = (NestedSingleton)constructor.newInstance();
            
        } catch (Exception e ) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } 
        System.out.println(nestedSingleton.hashCode());
        System.out.println(nestedSingleton2.hashCode());
    }
}

 

 為防止以上情況,可在構造器中拋出異常,以阻止新的實例產生

public class NestedSingleton {
    

    private NestedSingleton() {
        synchronized (NestedSingleton.class) {
            //判斷是否已有實例
        if(SingletonClassHolder.SINGLE_INSTANCE  != null){
            throw new RuntimeException("new another instance!");
        }
    }

}
//靜態內部類,只初始化一次
    private static class SingletonClassHolder {
        static final NestedSingleton SINGLE_INSTANCE = new NestedSingleton();
        
    }
//調用靜態內部類方法得到單例
    public static NestedSingleton getInstance() {
        return SingletonClassHolder.SINGLE_INSTANCE;
    }
}

 

 

附2 序列化(Serialization)導致的單例失效

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.OutputStream;

public class SingletonSerializedTest   {

    public static void main(String[] args) throws Exception {
        
        NestedSingleton nestedSingleton0 = NestedSingleton.getInstance();
        ObjectOutput output =null;
        OutputStream outputStream = new FileOutputStream("serializedFile.ser"); 
        output = new ObjectOutputStream(outputStream);
        output.writeObject(nestedSingleton0);
        output.close();
    
        ObjectInput input = new ObjectInputStream(new FileInputStream("serializedFile.ser"));
        NestedSingleton nestedSingleton1 = (NestedSingleton) input.readObject();
        input.close();
         
        System.out.println("nestedSingleton0 hashCode="+nestedSingleton0.hashCode());
        System.out.println("nestedSingleton1 hashCode="+nestedSingleton1.hashCode());
    
    
    }
}

輸出結果 

nestedSingleton0 hashCode=865113938
nestedSingleton1 hashCode=2003749087

顯然,產生了兩個實例

如果要避免這個情況,則需要利用ObjectInputStream.readObject()中的機制,它在調用readOrdinaryObject()后會判斷類中是否有ReadResolve()方法,如果有就采用類中的ReadResolve()新建實例

那么以上單例類就可以如下改造

import java.io.Serializable;

public class NestedSingleton implements Serializable {
    
    /**
     * 
     */
    private static final long serialVersionUID = 3934012982375502226L;
    private NestedSingleton() {
        synchronized (NestedSingleton.class) {
            //判斷是否已有實例
        if(SingletonClassHolder.SINGLE_INSTANCE  != null){
            throw new RuntimeException("new another instance!");
        }
    }
}
//靜態內部類,只初始化一次
    private static class SingletonClassHolder {
        static final NestedSingleton SINGLE_INSTANCE = new NestedSingleton();   
    }
//調用靜態內部類方法得到單例
    public static NestedSingleton getInstance() {
        return SingletonClassHolder.SINGLE_INSTANCE;
    }
    public void printFn() {
        // TODO Auto-generated method stub
        System.out.print("fine");
    }
     protected Object readResolve() {
        return getInstance();
    } 
}

再次使用序列化反序列化過程驗證,得到

nestedSingleton0 hashCode=865113938
nestedSingleton1 hashCode=865113938

這樣在序列化反序列化過程保證了單例的實現

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM