成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

從未這么明白的設(shè)計(jì)模式(一):?jiǎn)卫J?

NikoManiac / 1487人閱讀

摘要:一般來說,這種單例實(shí)現(xiàn)有兩種思路,私有構(gòu)造器,枚舉。而這種方式又分了飽漢式,餓漢式。通過關(guān)鍵字防止指令重排序。

什么是單例?為什么要用單例?

一個(gè)類被設(shè)計(jì)出來,就代表它表示具有某種行為(方法),屬性(成員變量),而一般情況下,當(dāng)我們想使用這個(gè)類時(shí),會(huì)使用new關(guān)鍵字,這時(shí)候jvm會(huì)幫我們構(gòu)造一個(gè)該類的實(shí)例。而我們知道,對(duì)于new這個(gè)關(guān)鍵字以及該實(shí)例,相對(duì)而言是比較耗費(fèi)資源的。所以如果我們能夠想辦法在jvm啟動(dòng)時(shí)就new好,或者在某一次實(shí)例new好以后,以后不再需要這樣的動(dòng)作,就能夠節(jié)省很多資源了。

哪些類可以使用單例?

一般而言,我們總是希望無狀態(tài)的類能夠設(shè)計(jì)成單例,那這個(gè)無狀態(tài)代表什么呢? 簡(jiǎn)單而言,對(duì)于同一個(gè)實(shí)例,如果多個(gè)線程同時(shí)使用,并且不使用額外的線程同步手段,不會(huì)出現(xiàn)線程同步的問題,我們就可以認(rèn)為是無狀態(tài)的,再簡(jiǎn)單點(diǎn):一個(gè)類沒有成員變量,或者它的成員變量也是無狀態(tài)的,我們就可以考慮設(shè)計(jì)成單例。

實(shí)現(xiàn)方法

好了,我們已經(jīng)知道什么是單例,為什么要使用單例了,那我們接下來繼續(xù)討論下怎么實(shí)現(xiàn)單例。 一般來說,我們可以把單例分為行為上的單例管理上的單例行為上的單例代表不管如何操作(此處不談cloneable,反射),至始至終jvm中都只有一個(gè)類的實(shí)例,而管理上的單例則可以理解為:不管誰去使用這個(gè)類,都要守一定的規(guī)矩,比方說,我們使用某個(gè)類,只能從指定的地方’去拿‘,這樣拿到就是同一個(gè)類了。 而對(duì)于管理上的單例,相信大家最為熟悉的就是spring了,spring將所有的類放到一個(gè)容器中,以后使用該類都從該容器去取,這樣就保證了單例。 所以這里我們剩下的就是接著來談?wù)勅绾螌?shí)現(xiàn)行為上的單例了。一般來說,這種單例實(shí)現(xiàn)有兩種思路,私有構(gòu)造器,枚舉。

枚舉實(shí)現(xiàn)單例

枚舉實(shí)現(xiàn)單例是最為推薦的一種方法,因?yàn)榫退阃ㄟ^序列化,反射等也沒辦法破壞單例性,例子:

public enum SingletonEnum {
    INSTANCE;

    public static void main(String[] args) {
        System.out.println(SingletonEnum.INSTANCE == SingletonEnum.INSTANCE);
    }
}

結(jié)果自然是true,而如果我們嘗試使用反射破壞單例性:

public enum BadSingletonEnum {
    /**
     *
     */
    INSTANCE;

    public static void main(String[] args) throws Exception{
        System.out.println(BadSingletonEnum.INSTANCE == BadSingletonEnum.INSTANCE);

        Constructor badSingletonEnumConstructor = BadSingletonEnum.class.getDeclaredConstructor();
        badSingletonEnumConstructor.setAccessible(true);
        BadSingletonEnum badSingletonEnum = badSingletonEnumConstructor.newInstance();

        System.out.println(BadSingletonEnum.INSTANCE == badSingletonEnum);
    }
}

結(jié)果如下:

Exception in thread "main" java.lang.NoSuchMethodException: cn.jsbintask.BadSingletonEnum.()
	at java.lang.Class.getConstructor0(Class.java:3082)
	at java.lang.Class.getDeclaredConstructor(Class.java:2178)
	at cn.jsbintask.BadSingletonEnum.main(BadSingletonEnum.java:18)

異常居然是沒有init方法,這是為什么呢? 那我們反編譯查看下這個(gè)枚舉類的字節(jié)碼:

// class version 52.0 (52)
// access flags 0x4031
// signature Ljava/lang/Enum;
// declaration: cn/jsbintask/BadSingletonEnum extends java.lang.Enum
public final enum cn/jsbintask/BadSingletonEnum extends java/lang/Enum {

  // compiled from: BadSingletonEnum.java

  // access flags 0x4019
  public final static enum Lcn/jsbintask/BadSingletonEnum; INSTANCE

  // access flags 0x101A
  private final static synthetic [Lcn/jsbintask/BadSingletonEnum; $VALUES
}

結(jié)果發(fā)現(xiàn)這個(gè)枚舉類繼承了抽象類java.lang.Enum,我們接著看下Enum,發(fā)現(xiàn)構(gòu)造器:

/**
    * Sole constructor.  Programmers cannot invoke this constructor.
    * It is for use by code emitted by the compiler in response to
    * enum type declarations.
    *
    * @param name - The name of this enum constant, which is the identifier
    *               used to declare it.
    * @param ordinal - The ordinal of this enumeration constant (its position
    *         in the enum declaration, where the initial constant is assigned
    *         an ordinal of zero).
*/
protected Enum(String name, int ordinal) {
    this.name = name;
    this.ordinal = ordinal;
}

那我們接著改變代碼,反射調(diào)用這個(gè)構(gòu)造器:

public enum BadSingletonEnum {
    /**
     *
     */
    INSTANCE();

    public static void main(String[] args) throws Exception{
        System.out.println(BadSingletonEnum.INSTANCE == BadSingletonEnum.INSTANCE);

        Constructor badSingletonEnumConstructor = BadSingletonEnum.class.getDeclaredConstructor(String.class, int.class);
        badSingletonEnumConstructor.setAccessible(true);
        BadSingletonEnum badSingletonEnum = badSingletonEnumConstructor.newInstance("test", 0);

        System.out.println(BadSingletonEnum.INSTANCE == badSingletonEnum);
    }
}

結(jié)果如下:

Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
	at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
	at cn.jsbintask.BadSingletonEnum.main(BadSingletonEnum.java:21)

這次雖然方法找到了,但是直接給我們了一句Cannot reflectively create enum objects,不能夠反射創(chuàng)造枚舉對(duì)象,接著我們繼續(xù)看下**newInstance(...)**這個(gè)方法:

public T newInstance(Object ... initargs)
        throws InstantiationException, IllegalAccessException,
               IllegalArgumentException, InvocationTargetException
    {
        if (!override) {
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                Class<");null, modifiers);
            }
        }
        if ((clazz.getModifiers() & Modifier.ENUM) != 0)
            throw new IllegalArgumentException("Cannot reflectively create enum objects");
        ConstructorAccessor ca = constructorAccessor;   // read volatile
        if (ca == null) {
            ca = acquireConstructorAccessor();
        }
        @SuppressWarnings("unchecked")
        T inst = (T) ca.newInstance(initargs);
        return inst;
    }

關(guān)鍵代碼就是:if ((clazz.getModifiers() & Modifier.ENUM) != 0) throw new IllegalArgumentException("Cannot reflectively create enum objects");,所以就是jdk從根本上拒絕了使用反射去創(chuàng)建(知道為啥java推薦使用enum實(shí)現(xiàn)單例了吧),另外,我們?cè)儆^察下Enum類的clone和序列化方法,如下:

protected final Object clone() throws CloneNotSupportedException {
    throw new CloneNotSupportedException();
}

private void readObject(ObjectInputStream in) throws IOException,
    ClassNotFoundException {
    throw new InvalidObjectException("can"t deserialize enum");
}

private void readObjectNoData() throws ObjectStreamException {
    throw new InvalidObjectException("can"t deserialize enum");
}

一眼看出,直接丟出異常,不允許這么做?。ㄕ嬗H兒子系列)。 所以,結(jié)論就是:枚舉是最靠譜的實(shí)現(xiàn)單例的方式!

私有構(gòu)造器

另外一個(gè)實(shí)現(xiàn)單例最普通的方法則是私有構(gòu)造器,開放獲取實(shí)例公共方法,雖然這種方法還是可以用clone,序列化,反射破壞單例性(除非特殊情況,我們不會(huì)這么做),但是卻是最容易理解使用的。而這種方式又分了飽漢式餓漢式。

餓漢式

看名字就知道,饑渴?。瓤?,開個(gè)玩笑),它指的是當(dāng)一個(gè)類被jvm加載的時(shí)候就會(huì)被實(shí)例化,這樣可以從根本上解決多個(gè)線程的同步問題,例子如下:

public class FullSingleton {
    private static FullSingleton ourInstance = new FullSingleton();

    public static FullSingleton getInstance() {
        return ourInstance;
    }

    private FullSingleton() {
    }

    public static void main(String[] args) {
        System.out.println(FullSingleton.getInstance() == FullSingleton.getInstance());
    }
}

結(jié)果自然是true,雖然這種做法很方便的幫我們解決了多線程實(shí)例化的問題,但是缺點(diǎn)也很明顯,因?yàn)檫@句代碼**private static FullSingleton ourInstance = new FullSingleton();**的關(guān)系,所以該類一旦被jvm加載就會(huì)馬上實(shí)例化,那如果我們不想用這個(gè)類怎么辦呢? 是不是就浪費(fèi)了呢?既然這樣,我們來看下替代方案! 飽漢式。

飽漢式

既然是,就代表它不著急,那我們可以這么寫:

public class HungryUnsafeSingleton {
    private static HungryUnsafeSingleton instance;
    
    public static HungryUnsafeSingleton getInstance() {
        if (instance == null) {
            instance = new HungryUnsafeSingleton();
        }
        
        return instance;
    }
    
    private HungryUnsafeSingleton() {}
}

用意很容易理解,就是用到**getInstance()**方法才去檢查instance,如果為null,就new一個(gè),這樣就不怕浪費(fèi)了,但是這個(gè)時(shí)候問題就來了:現(xiàn)在有這么一種情況,在有兩個(gè)線程同時(shí) 運(yùn)行到了 instane == null這個(gè)語句,并且都通過了,那他們就會(huì)都實(shí)例化一個(gè)對(duì)象,這樣就又不是單例了。既然這樣,哪有什么解決辦法呢? 鎖方法

    直接同步方法 這種方法比較干脆利落,那就是直接在getInstance()方法上加鎖,這樣就解決了線程問題:

public class HungrySafeSingleton {
    private static HungrySafeSingleton instance;

    public static synchronized HungrySafeSingleton getInstance() {
        if (instance == null) {
            instance = new HungrySafeSingleton();
        }

        return instance;
    }

    private HungrySafeSingleton() {
        System.out.println("HungryUnsafeSingleton.HungryUnsafeSingleton");
    }

    public static void main(String[] args) {
        System.out.println(HungrySafeSingleton.getInstance() == HungrySafeSingleton.getInstance());
    }
}

很簡(jiǎn)單,很容易理解,加鎖,只有一個(gè)線程能實(shí)例該對(duì)象。但是,此時(shí)問題又來了,我們知道對(duì)于靜態(tài)方法而言,synchronized關(guān)鍵字會(huì)鎖住整個(gè) Class,這時(shí)候又會(huì)有性能問題了(尼瑪墨跡),那有沒有優(yōu)化的辦法呢? 雙重檢查鎖

public class HungrySafeSingleton {
    private static volatile HungrySafeSingleton instance;

    public static HungrySafeSingleton getInstance() {
        /* 使用一個(gè)本地變量可以提高性能 */
        HungrySafeSingleton result = instance;

        if (result == null) {

            synchronized (HungrySafeSingleton.class) {

                result = instance;
                if (result == null) {
                    instance = result = new HungrySafeSingleton();
                }
            }
        }

        return result;
    }

    private HungrySafeSingleton() {
        System.out.println("HungryUnsafeSingleton.HungryUnsafeSingleton");
    }

    public static void main(String[] args) {
        System.out.println(HungrySafeSingleton.getInstance() == HungrySafeSingleton.getInstance());
    }
}

    synchronized關(guān)鍵字只加在了關(guān)鍵的地方,并且通過本地變量提高了性能(effective java),這樣線程安全并且不浪費(fèi)資源的單例就完成了。

    通過volitalile關(guān)鍵字防止指令重排序。 對(duì)其他線程可見。

關(guān)注我,這里只有干貨!

總結(jié)

本章,我們一步一步從什么是單例,到為什么要使用單例,再到怎么使用單例,并且從源碼角度分析了為什么枚舉是最適合的實(shí)現(xiàn)方式,然后接著講解了飽漢式,餓漢式的寫法以及好處,缺點(diǎn)。 例子源碼:github.com/jsbintask22…

本文原創(chuàng)地址:jsbintask的博客,轉(zhuǎn)載請(qǐng)注明出處。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/7202.html

相關(guān)文章

  • 從未這么明白設(shè)計(jì)模式(二):觀察者模式

    摘要:小麗總是會(huì)在朋友圈發(fā)布自己的各種生活狀態(tài)??偨Y(jié)我們從觀察者模式特點(diǎn)入手,通過一個(gè)案例,一步一步完善了觀察著的寫法,特點(diǎn)組后介紹了總已有的實(shí)現(xiàn)關(guān)注我,這里只有干貨同系列文章從未這么明白的設(shè)計(jì)模式一單例模式 showImg(https://segmentfault.com/img/remote/1460000018874501); 本文原創(chuàng)地址,我的博客:https://jsbintask...

    dockerclub 評(píng)論0 收藏0
  • 大話PHP設(shè)計(jì)模式單例模式

    摘要:上面是簡(jiǎn)單的單例模式,自己寫程序的話夠用了,如果想繼續(xù)延伸,請(qǐng)傳送至大話設(shè)計(jì)模式之單例模式升級(jí)版 看了那么多單例的介紹,都是上來就說怎么做,也沒見說為什么這么做的。那小的就來說說為什么會(huì)有單例這個(gè)模式以便更好的幫助初學(xué)者真正的理解這個(gè)設(shè)計(jì)模式,如果你是大神,也不妨看完指正一下O(∩_∩)O首先我不得不吐槽一下這個(gè)模式名字單例,初學(xué)者通過字面很難理解什么是單例,我覺得應(yīng)該叫唯一模式更貼切...

    VEIGHTZ 評(píng)論0 收藏0
  • Java設(shè)計(jì)模式-單例模式

    摘要:那有什么辦法保證只有一個(gè)領(lǐng)導(dǎo)人斯大林呢較常見的兩種方式餓漢式和懶漢式二實(shí)戰(zhàn)圖這里提示一點(diǎn),在學(xué)習(xí)設(shè)計(jì)模式的時(shí)候,圖會(huì)讓你更容易,而且深刻的去理解到該模式的核心。下一篇的設(shè)計(jì)模式是工廠方法模式。 ??就算不懂設(shè)計(jì)模式的兄弟姐妹們,想必也聽說過單例模式,并且在項(xiàng)目中也會(huì)用上。但是,真正理解和熟悉單例模式的人有幾個(gè)呢?接下來我們一起來學(xué)習(xí)設(shè)計(jì)模式中最簡(jiǎn)單的模式之一——單例模式 一、為什么叫單...

    Jensen 評(píng)論0 收藏0
  • Python單例模式(Singleton)N種實(shí)現(xiàn)

    摘要:本篇文章總結(jié)了目前主流的實(shí)現(xiàn)單例模式的方法供讀者參考。使用實(shí)現(xiàn)單例模式同樣,我們?cè)陬惖膭?chuàng)建時(shí)進(jìn)行干預(yù),從而達(dá)到實(shí)現(xiàn)單例的目的。 很多初學(xué)者喜歡用 全局變量 ,因?yàn)檫@比函數(shù)的參數(shù)傳來傳去更容易讓人理解。確實(shí)在很多場(chǎng)景下用全局變量很方便。不過如果代碼規(guī)模增大,并且有多個(gè)文件的時(shí)候,全局變量就會(huì)變得比較混亂。你可能不知道在哪個(gè)文件中定義了相同類型甚至重名的全局變量,也不知道這個(gè)變量在程序的某...

    Maxiye 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<