單例模式的幾種寫(xiě)法


(1)餓漢模式

public class Singleton{
    private Singleton(){};
    private static final Singleton INSTANCE = new Singleton();
    public static Singleton getInstance(){
        renturn INSTANCE;
    }
}

分析: 第一次加載到內(nèi)存中的就會(huì)被初始化 ,并對(duì)外提供一個(gè)獲取該實(shí)例對(duì)象的方法,優(yōu)點(diǎn):立即執(zhí)行初始化,線程安全。缺點(diǎn):會(huì)受到反射,序列化影響,浪費(fèi)資源

(2)懶漢模式:線程不安全,單線程下只在第一次調(diào)用時(shí)才會(huì)實(shí)例化

public class Singleton{
    private Singleton(){};
    private static Singleton singleton = null;
    public static Singleton getInstance(){
        if(singleton == null){
            singleton = new Singleton();
        }
        return singleton;
    }
}

懶漢加鎖模式(1):線程安全,但是每次調(diào)用都會(huì)加鎖,效率很低

public class Singleton{
    private Singleton(){};
    private static  Singleton singleton = null;
    public static synchronized Singleton getInstance(){
        if(singleton == null){
            singleton = new Singleton();
        }
        return singleton;
    }
}

雙重檢查鎖的懶漢單例模式(2):保證線程安全,只有在singleton為null時(shí)才會(huì)加鎖,優(yōu)化鎖帶來(lái)的消耗

public class Singleton{
    private static volatile Singleton singleton = null;
    private Singleton(){};
    public static Singleton getInstance(){
        if(singleton == null){
            synchronized(Singleton.class){
                if(singleton == null){
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

(3)靜態(tài)內(nèi)部類(lèi)延遲初始化,線程安全

public class Singleton {
   private Singleton(){};
   private static class initInstance{
      private static final Singleton SINGLETON = new Singleton();
   }
   public static Singleton getSingleton(){
      return initInstance.SINGLETON;
   }
}

分析:根據(jù)JVM規(guī)范,當(dāng)?shù)谝淮握{(diào)用Singleleton.getInstance( )時(shí),Singleleton被首次調(diào)用,JVM對(duì)其進(jìn)行初始化操作,此時(shí)不會(huì)調(diào)用Singleleton的構(gòu)造方法。接下來(lái)會(huì)調(diào)用getInstance()方法,又首次主動(dòng)使用initInstance()內(nèi)部類(lèi),所以JVM會(huì)對(duì)initInstance()進(jìn)行初始化操作,在這個(gè)初始化操作中,靜態(tài)常量INSTANCE被賦值時(shí)才調(diào)用Singleton()構(gòu)造方法,完成實(shí)例化并返回該對(duì)象。
當(dāng)再次調(diào)用Singleleton.getInstance( )時(shí),因?yàn)橐呀?jīng)進(jìn)行過(guò)初始化操作了,就不會(huì)再進(jìn)行初始化操作,直接返回INSTANCE常量。

解決反射與序列化的影響思路:(以餓漢單例模式為例)

public class Singleton{
   private Singleton(){
      //在私有的無(wú)參構(gòu)造器中進(jìn)行判斷,這樣可以解決反射問(wèn)題
      if (INSTANCE != null){
         throw new RuntimeException("禁止反射創(chuàng)建單例模式");
      }
   };
   private static final Singleton INSTANCE = new Singleton();
   public static Singleton getInstance(){
      return INSTANCE;
   }
   //添加readResolve()方法可以防止序列化問(wèn)題
   private Object readResolve() throws ObjectStreamException{
      return INSTANCE;
   }
}

枚舉類(lèi)單例模式:可以滿(mǎn)足線程安全,天生防止序列化生成新的實(shí)例,防止反射攻擊

public class Singleton{}

public enum SingletonEnum{
    INSTANCE;
    private Singleton singleton = null;
    private SingletonEnum(){
        singleton = new Singleton();
    }
    public Singleton getInstance(){
        return singleton;
    }
}

調(diào)用:
Singleton singleton = SingletonEnum.INSTANCE.getInstance();

Enum是一個(gè)普通的類(lèi),繼承自Java.lang.Enum類(lèi)

public enum SingletonEnum{
    INSTANCE;
}

將上面枚舉編譯后的字節(jié)碼反編譯,得到如下代碼:

public abstract class SingletonEnum extends Enum<SingletonEnum>{
    public static final SingletonEnum INSTANCE;
    public static SIngletonEnum[]values();
    public static SingletonEnum valuesof(String s);
    static{};
}

通過(guò)反編譯后得到的代碼可知:
INSTANCE 被聲明為static,虛擬機(jī)會(huì)保證一個(gè)類(lèi)的<clinit>() 方法在多線程環(huán)境中被正確的加鎖、同步。所以,枚舉實(shí)現(xiàn)是在實(shí)例化時(shí)是線程安全。

接下來(lái)再看序列化的問(wèn)題
在Java的規(guī)范中,每一個(gè)枚舉類(lèi)型及其定義的枚舉變量在JVM中都是唯一的,在枚舉類(lèi)型的序列化與反序列化中,JVM做了一些特殊的規(guī)定:在序列化時(shí),Java僅僅只是將枚舉對(duì)象的name屬性輸出到結(jié)果中,反序列化時(shí)則是通過(guò)Java.lang.Enum的valuesof()方法根據(jù)名稱(chēng)獲取枚舉對(duì)象。
比如:

public enum SingletonEnum{
    INSTANCE;
}

上面枚舉類(lèi)在序列化時(shí),只將 INSTANCE 這個(gè)名稱(chēng)輸出,反序列化時(shí),再通過(guò)這個(gè)名稱(chēng),尋找對(duì)應(yīng)的枚舉類(lèi)型,因此反序列化后的實(shí)例與之前被序列化的對(duì)象實(shí)例相同

接下來(lái)看反射的問(wèn)題
枚舉單例

public enum Singleton {
INSTANCE {

@Override
protected void read() {
System.out.println("read");
}

@Override
protected void write() {
System.out.println("write");
}

};
protected abstract void read();
protected abstract void write();
}

反編譯后

public abstract class Singleton extends Enum
{

    private Singleton(String s, int i)
    {
        super(s, i);
    }

    protected abstract void read();

    protected abstract void write();

    public static Singleton[] values()
    {
        Singleton asingleton[];
        int i;
        Singleton asingleton1[];
        System.arraycopy(asingleton = ENUM$VALUES, 0, asingleton1 = new Singleton[i = asingleton.length], 0, i);
        return asingleton1;
    }

    public static Singleton valueOf(String s)
    {
        return (Singleton)Enum.valueOf(singleton/Singleton, s);
    }

    Singleton(String s, int i, Singleton singleton)
    {
        this(s, i);
    }

    public static final Singleton INSTANCE;
    private static final Singleton ENUM$VALUES[];

    static
    {
        INSTANCE = new Singleton("INSTANCE", 0) {

            protected void read()
            {
                System.out.println("read");
            }

            protected void write()
            {
                System.out.println("write");
            }

        };
        ENUM$VALUES = (new Singleton[] {
            INSTANCE
        });
    }
}

①類(lèi)的修飾為abstract,沒(méi)法實(shí)例化
②在static中完成實(shí)例化的,所以線程安全

從源碼上來(lái)看
java.lang.reflect 中的 Constructor 的newInstance中規(guī)定了不準(zhǔn)通過(guò)反射創(chuàng)建枚舉對(duì)象
(反射在通過(guò)newInstance創(chuàng)建對(duì)象時(shí),會(huì)檢查該類(lèi)是否是枚舉類(lèi),如果是,則拋出異常,反射失敗。)

Constructor 的newInstance.png

參考文章:
singleton模式四種線程安全的實(shí)現(xiàn)

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

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容