真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

01.Singleton Pattern 單例模式

Singleton Pattern 單例模式,作為創(chuàng)建型模式的一種,其保證了類的實(shí)例對(duì)象只有一個(gè),并對(duì)外提供此唯一實(shí)例的訪問(wèn)接口

成都創(chuàng)新互聯(lián)公司是一家專業(yè)提供貢井企業(yè)網(wǎng)站建設(shè),專注與成都做網(wǎng)站、成都網(wǎng)站建設(shè)、html5、小程序制作等業(yè)務(wù)。10年已為貢井眾多企業(yè)、政府機(jī)構(gòu)等服務(wù)。創(chuàng)新互聯(lián)專業(yè)的建站公司優(yōu)惠進(jìn)行中。

概述

對(duì)于單例模式而言,其最核心的目的就是為了保證該類的實(shí)例對(duì)象是唯一的。為此一方面,需要將該類的構(gòu)造函數(shù)設(shè)為private,另一方面,該類需要在內(nèi)部完成實(shí)例的構(gòu)造并對(duì)外提供訪問(wèn)接口。單例模式的好處顯而易見(jiàn),可以避免頻繁創(chuàng)建、銷毀實(shí)例所帶來(lái)的性能開(kāi)銷;但其缺點(diǎn)也同樣明顯,此類不僅需要描述業(yè)務(wù)邏輯,同時(shí)還需要構(gòu)造出該類的唯一對(duì)象并對(duì)外提供訪問(wèn)接口,其顯然違背了單一職責(zé)原則

實(shí)現(xiàn)

單例模式的思想雖然簡(jiǎn)單易懂,但實(shí)現(xiàn)起來(lái)卻可謂是花樣繁多、妙不可言。這里來(lái)介紹幾種常見(jiàn)的單例模式的實(shí)現(xiàn)

餓漢式

如下實(shí)現(xiàn)最為簡(jiǎn)單,當(dāng) SingletonDemo1 類被加載到JVM中,即會(huì)完成實(shí)例化。即不是所謂的Lazy Load 延遲加載,故通常被稱之為 “餓漢式” 單例。其最大的問(wèn)題就在,可能構(gòu)造出來(lái)的實(shí)例對(duì)象從頭到尾沒(méi)有被使用過(guò)(沒(méi)有調(diào)用過(guò)getInstance方法),從而浪費(fèi)內(nèi)存。可能有人會(huì)對(duì)此有些困惑,SingletonDemo1 類被加載到JVM中了,那肯定是因?yàn)檎{(diào)用了getInstance方法啊。難道還有別的原因?答案是肯定的

這里,我們先簡(jiǎn)要補(bǔ)充一些類加載機(jī)制的相關(guān)知識(shí)點(diǎn)。我們知道Java中的類被加載到JVM中,通常會(huì)有如下幾個(gè)階段:加載、 驗(yàn)證、準(zhǔn)備、解析、初始化等。其中對(duì)于初始化階段而言,虛擬機(jī)規(guī)范嚴(yán)格規(guī)定了有且僅有以下5種情況必須立即對(duì)類進(jìn)行初始化(而加載、 驗(yàn)證、準(zhǔn)備顯然必須在此之前開(kāi)始):

  1. 遇到new、getstatic、putstatic或invokestatic類型的字節(jié)碼指令時(shí),在Java代碼層面上就是new對(duì)象、讀取或設(shè)置類的靜態(tài)變量(被final修飾、已在編譯期將結(jié)果放入常量池的靜態(tài)變量除外)、調(diào)用類的靜態(tài)方法
  2. 對(duì)該類使用反射
  3. 當(dāng)初始化一個(gè)類的時(shí)候,如果發(fā)現(xiàn)其父類還未初始化,則需要先初始化父類
  4. 當(dāng)JVM啟動(dòng)時(shí),虛擬機(jī)會(huì)先初始化開(kāi)發(fā)者所指定的主類(即main方法所在類)
  5. 當(dāng)使用JDK 1.7的動(dòng)態(tài)語(yǔ)言支持時(shí),如果一個(gè)java.lang.invoke.MethodHandle實(shí)例最后解析的結(jié)果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,且該方法句柄所對(duì)應(yīng)的類沒(méi)有進(jìn)行過(guò)初始化,則需要先觸發(fā)其初始化

說(shuō)到這里,大家可能就明白了,如果SingletonDemo1類中還有其他靜態(tài)方法,一旦被調(diào)用就會(huì)導(dǎo)致SingletonDemo1類被加載、初始化,此時(shí)即完成了實(shí)例的構(gòu)造。眾所周知,JVM保證了類加載過(guò)程的線程安全,所以餓漢式單例同樣是線程安全的

/**
 * 單例模式1,餓漢式
 */
public class SingletonDemo1 {
    private static SingletonDemo1 instance = new SingletonDemo1("我是餓漢式的單例");

    private String description;

    /**
     * 私有構(gòu)造器
     * @param description
     */
    private SingletonDemo1(String description) {
        this.description = description;
    }

    public void getInfo() {
        System.out.println(description);
    }

    /**
     * 提供實(shí)例的訪問(wèn)接口
     * @return
     */
    public static SingletonDemo1 getInstance() {
        return instance;
    }

    public static void main(String[] args) {
        SingletonDemo1 singletonDemo1 = SingletonDemo1.getInstance();
        singletonDemo1.getInfo();
    }
}

測(cè)試結(jié)果如下所示

懶漢式

前面說(shuō)到,餓漢式單例會(huì)導(dǎo)致內(nèi)存空間的浪費(fèi),那么有沒(méi)有辦法解決這個(gè)問(wèn)題呢?答案是有的,這就是”懶漢式”單例。顧名思義,其實(shí)例不是在類加載、初始化時(shí)被構(gòu)建的,而是在真正需要的時(shí)候才去創(chuàng)建,如下所示

/**
 * 單例模式2,線程不安全的懶漢式
 */
public class SingletonDemo2 {
    private static SingletonDemo2 instance = null;
    private String description;

    private SingletonDemo2(String description) {
        this.description = description;
    }

    public void getInfo() {
        System.out.println(description);
    }

    public static SingletonDemo2 getInstance() {
        if( instance==null ) {
            instance = new SingletonDemo2("我是線程不安全的懶漢式單例");
        }
        return instance;
    }

    public static void main(String[] args) {
        SingletonDemo2 singletonDemo2 = SingletonDemo2.getInstance();
        singletonDemo2.getInfo();
    }

}

測(cè)試結(jié)果如下所示

“懶漢式”單例雖然實(shí)現(xiàn)了Lazy Load延遲加載,但是其存在一個(gè)很嚴(yán)重的問(wèn)題,不是線程安全的。所以如果在多線程環(huán)境下,我們需要使用下面線程安全的”懶漢式”單例,其保障線程安全的手段也很簡(jiǎn)單,直接使用synchronized來(lái)修飾getInstance方法。這種辦法過(guò)于簡(jiǎn)單粗暴,同時(shí)會(huì)導(dǎo)致效率十分低下。實(shí)例一旦被構(gòu)造完畢后,由于鎖的存在,導(dǎo)致每次只能由一個(gè)線程可以獲取到實(shí)例對(duì)象

/**
 * 單例模式3, 線程安全但效率低下的懶漢式
 */
public class SingletonDemo3 {
    private static SingletonDemo3 intance = null;
    private String description;

    private SingletonDemo3(String description) {
        this.description = description;
    }

    public void getInfo() {
        System.out.printf(description);
    }

    public static synchronized SingletonDemo3 getInstance() {
        if( intance==null ) {
            intance = new SingletonDemo3("我是線程安全線程安全但效率低下的懶漢式單例");
        }
        return intance;
    }

    public static void main(String[] args) {
        SingletonDemo3 singletonDemo3 = SingletonDemo3.getInstance();
        singletonDemo3.getInfo();
    }
}

測(cè)試結(jié)果如下所示

基于DCL(Double-Checked Locking)雙重檢查鎖的單例

通過(guò)前面我們看到,無(wú)論是餓漢式單例還是懶漢式單例,其都有明顯的缺點(diǎn)。那么有沒(méi)有一種完美的單例?既可以實(shí)現(xiàn)Lazy Load延遲加載,又可以在保證線程安全的前提下依然具備較高的效率呢。答案是肯定——基于DCL(Double-Checked Locking)雙重檢查鎖的單例。其實(shí)現(xiàn)如下,該單例實(shí)現(xiàn)中進(jìn)行了兩次檢查。第一次檢查時(shí)如果發(fā)現(xiàn)實(shí)例已經(jīng)構(gòu)造完畢了,則無(wú)需加鎖直接返回實(shí)例對(duì)象即可。其保證了實(shí)例在構(gòu)建完成后,其他多個(gè)線程可以同時(shí)快速獲取該實(shí)例。第二次檢查時(shí)則是為了避免重復(fù)構(gòu)造實(shí)例,因?yàn)樵谶€未構(gòu)造實(shí)例前,可能會(huì)有多個(gè)線程通過(guò)了第一次檢查,準(zhǔn)備加鎖來(lái)構(gòu)造實(shí)例。在DCL的單例實(shí)現(xiàn)中,尤其需要注意的一點(diǎn)是靜態(tài)變量instance必須要使用volatile進(jìn)行修飾。其原因在于volatile禁止了指令的重排序。這里就此問(wèn)題再作一些詳細(xì)的解釋說(shuō)明:在JDK1.5之前的Java內(nèi)存模型中,雖然不允許volatile變量之間進(jìn)行重排序,但卻允許普通變量與volatile變量之間的重排序。所以在JSR 133(JDK 1.5)中對(duì)volatile變量的內(nèi)存語(yǔ)義進(jìn)一步增強(qiáng),即限制了普通變量與volatile變量之間是否可以重排序的具體場(chǎng)景。這也是為什么在JDK 1.5之前無(wú)法通過(guò)DCL實(shí)現(xiàn)一個(gè)線程安全的單例模式

/**
 * 單例模式4,基于DCL的線程安全的單例
 */
public class SingletonDemo4 {
    // 此處必須要使用volatile修飾!
    private static volatile SingletonDemo4 instance = null;
    private String description;

    private SingletonDemo4(String description) {
        this.description = description;
    }

    public void getInfo() {
        System.out.println(description);
    }

    public static SingletonDemo4 getInstance() {
        if( instance==null ) {  // 第一次檢查:如果實(shí)例已經(jīng)構(gòu)造完成則直接取,避免每次取之前需要獲取鎖
               synchronized (SingletonDemo4.class) {
                    if(instance==null) {    // 第二次檢查:避免構(gòu)造出多個(gè)實(shí)例
                        instance = new SingletonDemo4("我是基于DCL的線程安全的單例");
                    }
               }
        }
        return instance;
    }

    public static void main(String[] args) {
        SingletonDemo4 singletonDemo4 = SingletonDemo4.getInstance();
        singletonDemo4.getInfo();
    }

}

測(cè)試結(jié)果如下

基于靜態(tài)內(nèi)部類的單例

前面我們說(shuō)到的第一種單例實(shí)現(xiàn),之所以被稱為餓漢式、非延遲加載。其原因就在于類的加載、初始化不能100%保證是因?yàn)檎{(diào)用getInstance方法引起的。而這里我們通過(guò)靜態(tài)內(nèi)部類的方式來(lái)實(shí)現(xiàn)一個(gè)延遲加載的單例,代碼如下所示。當(dāng)調(diào)用外部類SingletonDemo5的一些靜態(tài)方法(當(dāng)然getInstance方法除外),只會(huì)加載、初始化外部類SingletonDemo5,而不會(huì)去初始化靜態(tài)內(nèi)部類SingletonDemo5Holder。只有通過(guò)調(diào)用getInstance方法訪問(wèn)了靜態(tài)內(nèi)部類SingletonDemo5Holder的靜態(tài)變量instance,靜態(tài)內(nèi)部類SingletonDemo5Holder才會(huì)被加載、初始化,顯然此時(shí)實(shí)例才會(huì)被真正的構(gòu)造。所以對(duì)于基于靜態(tài)內(nèi)部類的單例實(shí)現(xiàn)而言,其之所以能保證Lazy Load延遲加載特性,是其因?yàn)橥ㄟ^(guò)SingletonDemo5Holder靜態(tài)內(nèi)部類100%保證了靜態(tài)內(nèi)部類被加載、初始化是因?yàn)檎{(diào)用外部類的getInstance方法而導(dǎo)致的。同樣地,該方式的單例也是滿足線程安全的,原因在餓漢式單例實(shí)現(xiàn)中已作解釋,此處就不再贅述

/**
 * 單例模式5,靜態(tài)內(nèi)部類
 */
public class SingletonDemo5 {
    private String description;

    private SingletonDemo5(String description) {
        this.description = description;
    }

    public void getInfo() {
        System.out.println(description);
    }

    private static class SingletonDemo5Holder{
        private static final SingletonDemo5 instance = new SingletonDemo5("我是基于靜態(tài)內(nèi)部類的線程安全的單例");
    }

    public static SingletonDemo5 getInstance() {
        return SingletonDemo5Holder.instance;
    }

    public static void main(String[] args) {
        SingletonDemo5 singletonDemo5 = SingletonDemo5.getInstance();
        singletonDemo5.getInfo();
    }
}

測(cè)試結(jié)果如下所示

基于枚舉的單例

對(duì)于Java的枚舉類型而言,其構(gòu)造器是且只能是private私有的。故其特別適合用于實(shí)現(xiàn)單例模式。下面即是一個(gè)基于枚舉的單例實(shí)現(xiàn),可以看到此種實(shí)現(xiàn)非常簡(jiǎn)潔優(yōu)雅。當(dāng)枚舉類進(jìn)行加載、初始化時(shí),即會(huì)完成實(shí)例的構(gòu)建,我們通過(guò)枚舉的特性保證了實(shí)例的唯一性,當(dāng)然其不是Lazy Load延遲加載的。與此同時(shí)根據(jù)類的加載機(jī)制我們可知其也是線程安全的(由JVM保證)

/**
 * 單例模式6,枚舉法
 */
public enum SingletonDemo6 {
    INSTANCE("我是枚舉法的單例");

    private String description;

    /**
     * 枚舉的構(gòu)造器默認(rèn)訪問(wèn)權(quán)限是private, 當(dāng)然也只能是私有的
     * @param description
     */
    SingletonDemo6(String description) {
        this.description = description;
    }

    public void getInfo() {
        System.out.println(description);
    }
}
...
/**
 * 測(cè)試用例
 */
public class SingletonDemo6Test {
    public static void main(String[] args) {
        SingletonDemo6 singletonDemo6 = SingletonDemo6.INSTANCE;
        singletonDemo6.getInfo();
    }
}

測(cè)試結(jié)果如下

參考文獻(xiàn)

  1. Head First 設(shè)計(jì)模式 弗里曼著

名稱欄目:01.Singleton Pattern 單例模式
文章轉(zhuǎn)載:http://weahome.cn/article/dscgije.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部