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

什麼是單例模式?java

 

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

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

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

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

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

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

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

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

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

相關文章
相關標籤/搜索