【探索】單例模式與多線程

單例模式結合多線程技術

1.餓漢模式/“立即加載”

立即加載就是使用類的時候已經將對象創建完畢,常見的實現方法是直接new 實例化。

1.1 立即加載型單例模式

創建MyObject.java:

public class MyObject {
    private static MyObject myObject = new MyObject();
    private MyObject(){

    }

    public static MyObject getInstance(){
        return myObject;
    }
}

創建MyThread.java:

public class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println(MyObject.getInstance().hashCode());
    }
}

創建測試類Run.java:

public class Run {
    public static void main(String[] args) {
            MyThread a1 = new MyThread();
            MyThread a2 = new MyThread();
            MyThread a3 = new MyThread();
            a1.start();
            a2.start();
            a3.start();
    }
}

最後的運行結果如下,可見是同一個對象,即實現了立即加載型單例模式。

1097280301
1097280301
1097280301

2.延遲加載/“懶漢模式”

延遲加載就是在調用get()方法時實例才被創建,常見的實現方法就是在get()方法中進行new實例化,延遲加載也被稱作"懶漢模式"
修改MyObject類如下:

public class MyObject {
    private static MyObject myObject;
    private MyObject(){

    }
    public static MyObject getInstance(){
        //延遲加載
        if(myObject == null){
            myObject = new MyObject();
        }
        return myObject;
    }
}

最後的運行結果如下,能夠看到多線程環境中並不是同一個單例

980142845
1553816201
418194775
  • 上面的延遲加載/"餓漢模式"在多線程的環境中不能實現單例,解決方案如下:
    修改MyObject類如下:
public class MyObject {
    private static MyObject myObject;
    private MyObject(){
    }
    synchronized public static MyObject getInstance(){
        //延遲加載
        if(myObject == null) {
            myObject = new MyObject();
        }
        return myObject;
    }
}
1553816201
1553816201
1553816201

最後的運行結果雖然實現了是取的同一個實例,但是效率卻非常的低,嘗試同步代碼塊和直接同步方法是一樣的,效率也提高不了多少,最後採用DCL雙檢查鎖機制

DCL雙檢查鎖機制

修改MyObject類如下:

public class MyObject {
    private static MyObject myObject;
    private MyObject(){
    }
     public static MyObject getInstance(){
        //延遲加載
        if(myObject == null) {
            synchronized(MyObject.class){
                if(myObject == null){
                    myObject = new MyObject();
                }
            }
        }
        return  myObject;
    }
}

最後的運行結果仍然是單例的,且提升了效率

1230761675
1230761675
1230761675

使用靜態內置類實現單例模式

DCL可以解決多線程單例模式的非線程安全問題,當然其他的辦法也能達到同樣的效果
修改MyObject類如下:

public class MyObject {
    private MyObject(){
    }
     public static MyObject getInstance(){

        return  MyObjectHandler.myObject;
    }
    private static class MyObjectHandler{
        private static MyObject myObject = new MyObject();
    }

}

最後的運行結果如下:

964250181
964250181
964250181

序列化與反序列化對象的單例模式實現

靜態內置類可以達到線程安全問題,但如果遇到序列化對象時,使用默認的方式運行得到的結果還是多例的,解決辦法就是在反序列化中使用readResolve()方法

public class MyObject implements Serializable {
    private MyObject(){
    }
     public static MyObject getInstance(){
        return  MyObjectHandler.myObject;
    }
    private static class MyObjectHandler{
        private static MyObject myObject = new MyObject();
    }

    protected Object readResolve(){
        return MyObjectHandler.myObject;
    }
}

創建測試類:

public class Run {
    public static void main(String[] args) {
        try {
            MyObject myObject = MyObject.getInstance();
            FileOutputStream fosRef = new FileOutputStream(new File("MyObject.txt"));
            ObjectOutputStream fo = new ObjectOutputStream(fosRef);
            fo.writeObject(myObject);
            fo.close();
            fosRef.close();
            System.out.println(myObject.hashCode());
            FileInputStream flsRef = new FileInputStream("MyObject.txt");
            ObjectInputStream ioRef = new ObjectInputStream(flsRef);
            MyObject m  = (MyObject)ioRef.readObject();
            ioRef.close();
            flsRef.close();
            System.out.println(m.hashCode());
        }catch (Exception e){
            e.printStackTrace();
        }

    }
}

最後的運行結果顯示兩者是單例的。

325040804
325040804

使用static代碼塊來實現單例模式(靜態代碼塊中的代碼在使用類的時候就已經執行了)

修改MyObject類如下:

public class MyObject {
    private static MyObject instan = null;
    static {
        instan = new MyObject();
    }
    private MyObject(){
    }
    public static MyObject getInstance(){
        return  instan;
    }
}

創建Run.java類:

public class Run {
    public static void main(String[] args) {
        MyThread a1 = new MyThread();
        MyThread a2 = new MyThread();
        MyThread a3 = new MyThread();
        a1.start();
        a2.start();
        a3.start();
    }
}

最後的運行結果如下:

2049367639
2049367639
2049367639

使用Enum枚舉數據類型實現單例模式

枚舉Enum和靜態代碼塊的特性相似,在使用枚舉類時,構造方法會被自動調用。即把MyObject類定義爲枚舉類

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