單例模式的六種實現方式

2020-10-25 08:00:28

單例模式的六種實現方式

1、單例模式之餓漢式[可用]

//餓漢式實現單例模式
public class Hungry {

    private Hungry(){}

    private  static Hungry hungry = new Hungry();

    public static Hungry getHungry(){
        return hungry;
    }
}

優點:實現簡單,類載入的時候就完成了範例化,避免了執行緒的同步問題
缺點:無法實現延遲載入,可能會造成記憶體的浪費(浪費可忽略)

2、單例模式之懶漢式[不可用]

//懶漢式實現單例模式
public class LazySingleton01 {

    private LazySingleton01(){}

    private static LazySingleton01 lazy ;

    public static LazySingleton01 getLazy(){
        if(lazy==null){
            return new LazySingleton01();
        }
        return lazy;
    }
}

缺點:執行緒不安全,不推薦使用

存線上程安全問題的過程:在執行過程中可能會存在這麼一種情況,有多個執行緒去呼叫getLazy方法來獲取LazySingleton01範例,就有可能發生,第一個執行緒在執行if(lazy = =null)的語句時,還沒執行new LazySingleton01()時,第二個執行緒獲得的lazy也為null,通過了(lazy==null)的判斷,最終兩個執行緒都會new範例。

3、單例模式之懶漢式+同步方法[不推薦使用]

//懶漢式+同步方法實現單例模式
public class LazySingleton02 {
    private LazySingleton02(){}

    private static LazySingleton02 lazy;

    public static synchronized LazySingleton02 getLazy(){
        if(lazy==null){
            return new LazySingleton02();
        }
        return lazy;
    }
}

執行緒安全,效率低不推薦使用

對於上述缺陷的改進可能有的人會想到如下的程式碼:

public class LazySingleton03 {
    private LazySingleton03(){}

    private static LazySingleton03 lazy;

    public static  LazySingleton03 getLazy(){
        if(lazy==null){
        	synchronized(LazySingleton03.class){
            	return new LazySingleton03();
            }
        }
        return lazy;
    }
}

該寫法一樣是執行緒不安全的,當一個執行緒還沒有範例化LazySingleton03時,另一個執行緒執行到if(lazy==null)這個判斷語句時就會進入if語句,雖然加了鎖,但是等到第一個執行緒執行完lazy=new LazySingleton03()釋放出這個鎖時,另一個進入if語句的執行緒同樣會範例化另外一個LazySingleton03物件。

經過一步步的探索,有了雙重校驗鎖的懶漢式寫法:

4、單例模式之懶漢式+雙重校驗鎖[推薦使用]

//雙重校驗鎖+懶漢式
public class DoubleCheckSingleton {
    private DoubleCheckSingleton(){}
    
    private volatile static DoubleCheckSingleton instance;
    
    public static DoubleCheckSingleton getInstance(){
        if(instance==null){
            synchronized (DoubleCheckSingleton.class){
                if(instance==null){
                    return new DoubleCheckSingleton();
                }
            }
        }
        return instance;
    }
}

優點:執行緒安全,延遲載入,效率較高

注意此處的volatile:

//雙重校驗鎖+懶漢式
public class DoubleCheckSingleton {
    private Socket socket;

    private DoubleCheckSingleton(){
        this.socket=new Socket();
    }
    
    private volatile static DoubleCheckSingleton instance;
    public static DoubleCheckSingleton getInstance(){
        if(instance==null){
            synchronized (DoubleCheckSingleton.class){
                if(instance==null){
                    return new DoubleCheckSingleton();
                }
            }
        }
        return instance;
    }
}

根據JVM執行時指令重排序和Happens-Before的規則,instance和socket之間的範例化的順序並沒有什麼關係約束,誰在前誰在後都有可能,如果instance最先範例化,那麼socket就不會範例化,呼叫這個方法就會產生空指標異常------>

解決這個方法很簡單,加一個關鍵字 volatite
這個關鍵字能夠保證範例化的順序的相對位置不變,instance範例化永遠在socket之後

5、單例模式之靜態內部類[推薦使用]

//靜態內部類實現單例模式
public class StaticInner {

    private StaticInner(){}

    private static class inner{
        private static StaticInner instance=new StaticInner();
    }

    public static StaticInner getInstance(){
        return inner.instance;
    }
}

優點:避免了執行緒不安全,延遲載入,效率高
詳解:這個方法並沒有事先宣告instance的靜態成員,而是把它放到了靜態內部類inner中,inner中定義了靜態變數,並直接進行了範例化,當inner被主動參照的時候就會建立範例,StaticInner在範例建立過程中被收集到()方法中,這個方法是同步方法,
保證了記憶體的可見性,JVM指令的順序執行和原子性。該方法也是單例模式的最好設計之一。

6、單例模式之列舉[推薦使用]

//列舉實現單例模式
public enum EnumSingleton {
    INSTANCE;

    private EnumSingleton(){}

    public void method(){}
}

藉助JDK1.5中新增的列舉來實現單例模式
優點:不僅能避免多執行緒同步問題,而且還能防止反序列化重新建立新的物件,實現非常簡單而且最安全可謂很完美。