Singleton Pattern in Java

單例模式

單例模式是一種軟件工程設計解決方案,適用于應用程序希望所有情況下當前進程中都只有一個類的實例化對象。在Java編程應用中單例模式應用非常的廣泛,而且能夠解決Java應用程序很多的安全問題。

我們分別討論一下單例模式在Java應用中的幾種經(jīng)典實現(xiàn)方式。

餓漢模式

單例模式實現(xiàn)方案的一種,單例的實例化在單例對象實際需要之前已經(jīng)被初始化于進程環(huán)境中,在Java應用中在JVM的類加載機制加載類字節(jié)流鏈接與初始化階段便實例化完成。

package com.iblog.pattern.singleton;

public class UrgentSingleton {

    private static UrgentSingleton INSTANCE = new UrgentSingleton();

    /** private constructor */
    private UrgentSingleton() {}

    public static UrgentSingleton get() {
        return INSTANCE;
    }
}

餓漢模式單例實例實在應用初始化的時候便實例化單例,無論此單例對象是否被使用都會被初始化,如果單例對象很小,這是一種理想的實現(xiàn)方案,我們可能接受即使不被使用也存在于JVM中;但是如果對象很大,那么這將是一種不太理想的方案,因為應用初始化時便實例化一個大對象于JVM環(huán)境中。

懶漢模式

在編程中我們習慣性在需要使用時才會去創(chuàng)建對象實例,那么單例的實現(xiàn)其實也是可以的,我們將單例的實例化發(fā)生在我們第一次需要使用這個單例的時候,即懶漢模式。

package com.iblog.pattern.singleton;

public class LazySingleton {
    private static volatile LazySingleton INSTANCE = null;

    private LazySingleton() {}

    public static LazySingleton get() {
        if (INSTANCE == null) {
            synchronized (LazySingleton.class) {
                if (INSTANCE == null) { // double check.
                    INSTANCE = new LazySingleton();
                }
            }
        }
        return INSTANCE;
    }
}

上面這個實現(xiàn)方案是當程序第一次調用獲取單例方法時檢查單例對象是否為null,如果為null嘗試獲取類鎖并實例化此單例對象,最后返回實例化對象。

懶漢模式需要注意的是,如果當前有多個線程同時進入get方法,判斷單例對象為null同時成立,那么這三個線程均會進入單例初始化邏輯,即獲取類鎖并初始化單例實例,所以在此處需要進行雙重驗證,否則會出現(xiàn)多個單例實例對象的情況發(fā)生。

靜態(tài)代碼塊實現(xiàn)單例

我們也可以利用JVM的類加載機制來用靜態(tài)代碼塊實現(xiàn)單例模式,單例的實例化放在靜態(tài)代碼塊中實現(xiàn);類的靜態(tài)代碼塊只會被類調用執(zhí)行一次,發(fā)生在類初始化時,可以確保對象只會被實例化一次。

package com.iblog.pattern.singleton;

public class StaticBlockSingleton {
    private static final StaticBlockSingleton INSTANCE;

    static {
        try {
            INSTANCE = new StaticBlockSingleton();
        } catch (Exception e) {
            throw new RuntimeException("StaticBlockSingleton init exception:", e);
        }
    }

    private StaticBlockSingleton() {}

    public static StaticBlockSingleton get() {
        return INSTANCE;
    }
}

靜態(tài)內部類實現(xiàn)單例

Java靜態(tài)內部類初始化發(fā)生在類被調用的時候,可以將單例實例化放入內部靜態(tài)類中,也能保證單例的唯一存在。

package com.iblog.pattern.singleton;

public class StaticClassSingleton {

    private static class LazyHolder {
        private static final StaticClassSingleton INSTANCE = new StaticClassSingleton();
    }
    
    private StaticClassSingleton() {}

    public static StaticClassSingleton get() {
        return LazyHolder.INSTANCE;
    }
}

采用靜態(tài)內部類封裝單例的靜態(tài)成員,可以實現(xiàn)部分屬性懶加載;在我們調用get獲取實例之前LazyHolder不會被初始化,也是被廣泛推薦的一種單例實現(xiàn)方案。

枚舉實現(xiàn)單例

Java中的枚舉特性保證了實例的單一性并且提供線程安全的隱式支持,將單例對象使用枚舉封裝也可以保證單例唯一存在。

public enum EnumSingleton {
    INSTANCE;
    public void someMethod(String param) {
        // some class member
    }
}

枚舉的實現(xiàn)方式在開源代碼中也經(jīng)常能見到。

readResolve()實現(xiàn)單例反序列化

分布式應用從同一個持久化源中讀取并反序列化一個單例對象,正常情況下對象反序列化會創(chuàng)建一個新的對象。

package com.iblog.pattern.singleton;

import java.io.Serializable;

public class Demo2Singleton implements Serializable {
    private volatile static Demo2Singleton instance = null;

    public static Demo2Singleton get() {
        if (instance == null) {
            instance = new Demo2Singleton();
        }
        return instance;
    }

    private int value = 10;

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }
}

以下測試用例會通過:

package com.iblog.pattern.singleton;

import org.junit.Test;

import java.io.*;

import static org.junit.Assert.*;

public class Demo2SingletonTest {
    private Demo2Singleton instanceOne = Demo2Singleton.get();

    @Test
    public void testReadSolve() throws Exception {

        // Serialize to a file
        ObjectOutput out = new ObjectOutputStream(new FileOutputStream(
                "filename.ser"));
        out.writeObject(instanceOne);
        out.close();

        instanceOne.setValue(20);

        // Serialize to a file
        ObjectInput in = new ObjectInputStream(new FileInputStream(
                "filename.ser"));
        Demo2Singleton instanceTwo = (Demo2Singleton) in.readObject();
        in.close();

        assertNotEquals(instanceOne.toString(), instanceTwo.toString());
        assertEquals(20, instanceOne.getValue());
        assertEquals(10, instanceTwo.getValue());

    }

}

我們從測試用例代碼可以看到,反序列化對象與原單例已經(jīng)不是一個對象,而是一個新的對象。我們需要保證單例,可以在單例實現(xiàn)類中添加readResolve()方法來確保單例對象的唯一性。

package com.iblog.pattern.singleton;

import java.io.Serializable;

public class DemoSingleton implements Serializable {
    private volatile static DemoSingleton instance = null;

    public static DemoSingleton get() {
        if (instance == null) {
            instance = new DemoSingleton();
        }
        return instance;
    }

    protected Object readResolve() {
        return instance;
    }

    private int value = 10;

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }
}

為單例添加測試用例:

package com.iblog.pattern.singleton;

import org.junit.Test;

import java.io.*;

import static org.junit.Assert.*;

public class DemoSingletonTest {
    private DemoSingleton instanceOne = DemoSingleton.get();

    @Test
    public void testReadSolve() throws Exception {

        // Serialize to a file
        ObjectOutput out = new ObjectOutputStream(new FileOutputStream(
                "filename.ser"));
        out.writeObject(instanceOne);
        out.close();

        instanceOne.setValue(20);

        // Serialize to a file
        ObjectInput in = new ObjectInputStream(new FileInputStream(
                "filename.ser"));
        DemoSingleton instanceTwo = (DemoSingleton) in.readObject();
        in.close();

        assertEquals(instanceOne.toString(), instanceTwo.toString());
        assertEquals(instanceOne.getValue(), instanceTwo.getValue());

    }

}

測試用例會順利通過,表明反序列化對象與原單例對象是同一個對象。

serialVersionUId實現(xiàn)反序列化

反序列化目標類如果發(fā)生結構變化,JVM會拋出異常:

java.io.InvalidClassException: singleton.DemoSingleton; local class incompatible: stream classdesc serialVersionUID = 5026910492258526905, local class serialVersionUID = 3597984220566440782
at java.io.ObjectStreamClass.initNonProxy(Unknown Source)
at java.io.ObjectInputStream.readNonProxyDesc(Unknown Source)
at java.io.ObjectInputStream.readClassDesc(Unknown Source)
at java.io.ObjectInputStream.readOrdinaryObject(Unknown Source)
at java.io.ObjectInputStream.readObject0(Unknown Source)
at java.io.ObjectInputStream.readObject(Unknown Source)
at singleton.SerializationTest.main(SerializationTest.java:24)
private static final long serialVersionUID = 1L;

Summary

單例模式最佳推薦方案推薦使用靜態(tài)內部類將單例封裝。

package com.iblog.pattern.singleton;

import java.io.Serializable;

public class StaticClassSingleton implements Serializable {
    private static final long serialVersionUID = 1L;

    private static class LazyHolder {
        private static final StaticClassSingleton INSTANCE = new StaticClassSingleton();
    }

    private StaticClassSingleton() {}

    public static StaticClassSingleton get() {
        return LazyHolder.INSTANCE;
    }
}

github:pattern-example

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容