国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專(zhuān)欄INFORMATION COLUMN

Java設(shè)計(jì)模式--單例模式

StonePanda / 3190人閱讀

摘要:所以這段代碼也就避免了代碼一中,可能出現(xiàn)因?yàn)槎嗑€程導(dǎo)致多個(gè)實(shí)例的情況。從內(nèi)部看是一個(gè)餓漢式的單例,但是從外部看來(lái),又的確是懶漢式的實(shí)現(xiàn)枚舉使用是不是很簡(jiǎn)單而且因?yàn)樽詣?dòng)序列化機(jī)制,保證了線程的絕對(duì)安全。

在介紹單例模式之前,我們先了解一下,什么是設(shè)計(jì)模式?
設(shè)計(jì)模式(Design Pattern):是一套被反復(fù)使用,多數(shù)人知曉的,經(jīng)過(guò)分類(lèi)編目的,代碼設(shè)計(jì)經(jīng)驗(yàn)的總結(jié)。
目的:使用設(shè)計(jì)模式是為了可重用性代碼,讓代碼更容易被他人理解,保證代碼可靠性。

本文將會(huì)用到的關(guān)鍵詞:

單例:Singleton

實(shí)例:instance

同步:synchronized

類(lèi)裝載器:ClassLoader

單例模式:
單例,顧名思義就是只能有一個(gè)、不能再出現(xiàn)第二個(gè)。就如同地球上沒(méi)有兩片一模一樣的樹(shù)葉一樣。

在這里就是說(shuō):一個(gè)類(lèi)只能有一個(gè)實(shí)例,并且整個(gè)項(xiàng)目系統(tǒng)都能訪問(wèn)該實(shí)例。

單例模式共分為兩大類(lèi):

懶漢模式:實(shí)例在第一次使用時(shí)創(chuàng)建

餓漢模式:實(shí)例在類(lèi)裝載時(shí)創(chuàng)建

單例模式UML圖

餓漢模式

按照定義我們可以寫(xiě)出一個(gè)基本代碼:

public class Singleton {

    // 使用private將構(gòu)造方法私有化,以防外界通過(guò)該構(gòu)造方法創(chuàng)建多個(gè)實(shí)例
    private Singleton() {
    }

    // 由于不能使用構(gòu)造方法創(chuàng)建實(shí)例,所以需要在類(lèi)的內(nèi)部創(chuàng)建該類(lèi)的唯一實(shí)例
    // 使用static修飾singleton 在外界可以通過(guò)類(lèi)名調(diào)用該實(shí)例   類(lèi)名.成員名
    static Singleton singleton = new Singleton();   // 1
    
    // 如果使用private封裝該實(shí)例,則需要添加get方法實(shí)現(xiàn)對(duì)外界的開(kāi)放
    private static Singleton instance = new Singleton();    // 2
    // 添加static,將該方法變成類(lèi)所有   通過(guò)類(lèi)名訪問(wèn)
    public static Singleton getInstance(){
        return instance;
    }
    
    //1和2選一種即可,推薦2
}

對(duì)于餓漢模式來(lái)說(shuō),這種寫(xiě)法已經(jīng)很‘perfect’了,唯一的缺點(diǎn)就是,由于instance的初始化是在類(lèi)加載時(shí)進(jìn)行的,類(lèi)加載是由ClassLoader來(lái)實(shí)現(xiàn)的,如果初始化太早,就會(huì)造成資源浪費(fèi)。
當(dāng)然,如果所需的單例占用的資源很少,并且也不依賴(lài)于其他數(shù)據(jù),那么這種實(shí)現(xiàn)方式也是很好的。

類(lèi)裝載的時(shí)機(jī):

new一個(gè)對(duì)象時(shí)

使用反射創(chuàng)建它的實(shí)例時(shí)

子類(lèi)被加載時(shí),如果父類(lèi)還沒(méi)有加載,就先加載父類(lèi)

JVM啟動(dòng)時(shí)執(zhí)行主類(lèi) 會(huì)先被加載

懶漢模式

懶漢模式的代碼如下

// 代碼一
public class Singleton {
    private static Singleton instance = null;
    private Singleton(){
    }
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton(); 
        }        
        return instance; 
   }
}

每次獲取instance之前先進(jìn)行判斷,如果instance為空就new一個(gè)出來(lái),否則就直接返回已存在的instance。

這種寫(xiě)法在單線程的時(shí)候是沒(méi)問(wèn)題的。但是,當(dāng)有多個(gè)線程一起工作的時(shí)候,如果有兩個(gè)線程同時(shí)運(yùn)行到 if (instance == null),都判斷為null(第一個(gè)線程判斷為空之后,并沒(méi)有繼續(xù)向下執(zhí)行,當(dāng)?shù)诙€(gè)線程判斷的時(shí)候instance依然為空),最終兩個(gè)線程就各自會(huì)創(chuàng)建一個(gè)實(shí)例出來(lái)。這樣就破環(huán)了單例模式 實(shí)例的唯一性

要想保證實(shí)例的唯一性就需要使用synchronized,加上一個(gè)同步鎖

// 代碼二
public class Singleton {
    private static Singleton instance = null;
    private Singleton() {}
    
    public static Singleton getInstance() {
        synchronized(Singleton.class){
            if (instance == null)
                instance = new Singleton();
        }
        return instance;
    }
}

加上synchronized關(guān)鍵字之后,getInstance方法就會(huì)鎖上了。如果有兩個(gè)線程(T1、T2)同時(shí)執(zhí)行到這個(gè)方法時(shí),會(huì)有其中一個(gè)線程T1獲得同步鎖,得以繼續(xù)執(zhí)行,而另一個(gè)線程T2則需要等待,當(dāng)?shù)赥1執(zhí)行完畢getInstance之后(完成了null判斷、對(duì)象創(chuàng)建、獲得返回值之后),T2線程才會(huì)執(zhí)行執(zhí)行。

所以這段代碼也就避免了代碼一中,可能出現(xiàn)因?yàn)槎嗑€程導(dǎo)致多個(gè)實(shí)例的情況。但是,這種寫(xiě)法也有一個(gè)問(wèn)題:給getInstance方法加鎖,雖然避免了可能會(huì)出現(xiàn)的多個(gè)實(shí)例問(wèn)題,但是會(huì)強(qiáng)制除T1之外的所有線程等待,實(shí)際上會(huì)對(duì)程序的執(zhí)行效率造成負(fù)面影響。

雙重檢查(Double-Check)

代碼二相對(duì)于代碼一的效率問(wèn)題,其實(shí)是為了解決1%幾率的問(wèn)題,而使用了一個(gè)100%出現(xiàn)的防護(hù)盾。那有一個(gè)優(yōu)化的思路,就是把100%出現(xiàn)的防護(hù)盾,也改為1%的幾率出現(xiàn),使之只出現(xiàn)在可能會(huì)導(dǎo)致多個(gè)實(shí)例出現(xiàn)的地方。
代碼如下:

// 代碼三
public class Singleton {
    private static Singleton instance = null;
    private Singleton() {}
    
    public static Singleton getInstance() {
        if (instance == null){
            synchronized(Singleton.class){
                if (instance == null)
                    instance = new Singleton();
            }
        }
        return instance;
    }
}

這段代碼看起來(lái)有點(diǎn)復(fù)雜,注意其中有兩次if(instance==null)的判斷,這個(gè)叫做『雙重檢查 Double-Check』。

第一個(gè) if(instance==null),其實(shí)是為了解決代碼二中的效率問(wèn)題,只有instance為null的時(shí)候,才進(jìn)入synchronized的代碼段大大減少了幾率。

第二個(gè)if(instance==null),則是跟代碼二一樣,是為了防止可能出現(xiàn)多個(gè)實(shí)例的情況。

這段代碼看起來(lái)已經(jīng)完美無(wú)瑕了。當(dāng)然,只是『看起來(lái)』,還是有小概率出現(xiàn)問(wèn)題的。想要充分理解需要先弄清楚以下幾個(gè)概念:原子操作、指令重排。

原子操作
簡(jiǎn)單來(lái)說(shuō),原子操作(atomic)就是不可分割的操作,在計(jì)算機(jī)中,就是指不會(huì)因?yàn)榫€程調(diào)度被打斷的操作。比如,簡(jiǎn)單的賦值是一個(gè)原子操作:

m = 6; // 這是個(gè)原子操作

假如m原先的值為0,那么對(duì)于這個(gè)操作,要么執(zhí)行成功m變成了6,要么是沒(méi)執(zhí)行 m還是0,而不會(huì)出現(xiàn)諸如m=3這種中間態(tài)——即使是在并發(fā)的線程中。

但是,聲明并賦值就不是一個(gè)原子操作:

int  n=6;//這不是一個(gè)原子操作

對(duì)于這個(gè)語(yǔ)句,至少有兩個(gè)操作:①聲明一個(gè)變量n ②給n賦值為6——這樣就會(huì)有一個(gè)中間狀態(tài):變量n已經(jīng)被聲明了但是還沒(méi)有被賦值的狀態(tài)。這樣,在多線程中,由于線程執(zhí)行順序的不確定性,如果兩個(gè)線程都使用m,就可能會(huì)導(dǎo)致不穩(wěn)定的結(jié)果出現(xiàn)。

指令重排
簡(jiǎn)單來(lái)說(shuō),就是計(jì)算機(jī)為了提高執(zhí)行效率,會(huì)做的一些優(yōu)化,在不影響最終結(jié)果的情況下,可能會(huì)對(duì)一些語(yǔ)句的執(zhí)行順序進(jìn)行調(diào)整。比如,這一段代碼:

int a ;   // 語(yǔ)句1 
a = 8 ;   // 語(yǔ)句2
int b = 9 ;     // 語(yǔ)句3
int c = a + b ; // 語(yǔ)句4

正常來(lái)說(shuō),對(duì)于順序結(jié)構(gòu),執(zhí)行的順序是自上到下,也即1234。但是,由于指令重排
的原因,因?yàn)椴挥绊懽罱K的結(jié)果,所以,實(shí)際執(zhí)行的順序可能會(huì)變成3124或者1324。

由于語(yǔ)句3和4沒(méi)有原子性的問(wèn)題,語(yǔ)句3和語(yǔ)句4也可能會(huì)拆分成原子操作,再重排。——也就是說(shuō),對(duì)于非原子性的操作,在不影響最終結(jié)果的情況下,其拆分成的原子操作可能會(huì)被重新排列執(zhí)行順序。

OK,了解了原子操作和指令重排的概念之后,我們?cè)倮^續(xù)看代碼三的問(wèn)題。

主要在于singleton = new Singleton()這句,這并非是一個(gè)原子操作,事實(shí)上在 JVM 中這句話大概做了下面 3 件事情。
  1. 給 singleton 分配內(nèi)存
  2. 調(diào)用 Singleton 的構(gòu)造函數(shù)來(lái)初始化成員變量,形成實(shí)例
  3. 將singleton對(duì)象指向分配的內(nèi)存空間(執(zhí)行完這步 singleton才是非 null了)

在JVM的即時(shí)編譯器中存在指令重排序的優(yōu)化。
  
也就是說(shuō)上面的第二步和第三步的順序是不能保證的,最終的執(zhí)行順序可能是 1-2-3 也可能是 1-3-2。如果是后者,則在 3 執(zhí)行完畢、2 未執(zhí)行之前,被線程二搶占了,這時(shí) instance 已經(jīng)是非 null 了(但卻沒(méi)有初始化),所以線程二會(huì)直接返回 instance,然后使用,然后順理成章地報(bào)錯(cuò)。
  
再稍微解釋一下,就是說(shuō),由于有一個(gè)『instance已經(jīng)不為null但是仍沒(méi)有完成初始化』的中間狀態(tài),而這個(gè)時(shí)候,如果有其他線程剛好運(yùn)行到第一層if (instance ==null)這里,這里讀取到的instance已經(jīng)不為null了,所以就直接把這個(gè)中間狀態(tài)的instance拿去用了,就會(huì)產(chǎn)生問(wèn)題。這里的關(guān)鍵在于線程T1對(duì)instance的寫(xiě)操作沒(méi)有完成,線程T2就執(zhí)行了讀操作。
  
對(duì)于代碼三出現(xiàn)的問(wèn)題,解決方案為:給instance的聲明加上volatile關(guān)鍵字
代碼如下:

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

volatile關(guān)鍵字的一個(gè)作用是禁止指令重排,把instance聲明為volatile之后,對(duì)它的寫(xiě)操作就會(huì)有一個(gè)內(nèi)存屏障,這樣,在它的賦值完成之前,就不用會(huì)調(diào)用讀操作。

注意:volatile阻止的不是singleton = new Singleton()這句話內(nèi)部[1-2-3]的指令重排,而是保證了在一個(gè)寫(xiě)操作([1-2-3])完成之前,不會(huì)調(diào)用讀操作(if (instance == null))。

其它方法 靜態(tài)內(nèi)部類(lèi)
public class Singleton {
   private static class SingletonHolder {
       private static final Singleton INSTANCE = new Singleton();
   }
   private Singleton (){}
   public static final Singleton getInstance() {
       return SingletonHolder.INSTANCE;
   }
}

這種寫(xiě)法的巧妙之處在于:對(duì)于內(nèi)部類(lèi)SingletonHolder,它是一個(gè)餓漢式的單例實(shí)現(xiàn),在SingletonHolder初始化的時(shí)候會(huì)由ClassLoader來(lái)保證同步,使INSTANCE是一個(gè)真單例。

同時(shí),由于SingletonHolder是一個(gè)內(nèi)部類(lèi),只在外部類(lèi)的Singleton的getInstance()中被使用,所以它被加載的時(shí)機(jī)也就是在getInstance()方法第一次被調(diào)用的時(shí)候。
  
它利用了ClassLoader來(lái)保證了同步,同時(shí)又能讓開(kāi)發(fā)者控制類(lèi)加載的時(shí)機(jī)。從內(nèi)部看是一個(gè)餓漢式的單例,但是從外部看來(lái),又的確是懶漢式的實(shí)現(xiàn)

枚舉
public enum SingleInstance {
  INSTANCE;
   public void fun1() {
       // do something
   }
}// 使用SingleInstance.INSTANCE.fun1();

是不是很簡(jiǎn)單?而且因?yàn)樽詣?dòng)序列化機(jī)制,保證了線程的絕對(duì)安全。三個(gè)詞概括該方式:簡(jiǎn)單、高效、安全

這種寫(xiě)法在功能上與共有域方法相近,但是它更簡(jiǎn)潔,無(wú)償?shù)靥峁┝诵蛄谢瘷C(jī)制,絕對(duì)防止對(duì)此實(shí)例化,即使是在面對(duì)復(fù)雜的序列化或者反射攻擊的時(shí)候。雖然這中方法還沒(méi)有廣泛采用,但是單元素的枚舉類(lèi)型已經(jīng)成為實(shí)現(xiàn)Singleton的最佳方法。

原文地址

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

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/69226.html

相關(guān)文章

  • 單例模式你會(huì)幾種寫(xiě)法?

    摘要:使用靜態(tài)類(lèi)體現(xiàn)的是基于對(duì)象,而使用單例設(shè)計(jì)模式體現(xiàn)的是面向?qū)ο?。二編?xiě)單例模式的代碼編寫(xiě)單例模式的代碼其實(shí)很簡(jiǎn)單,就分了三步將構(gòu)造函數(shù)私有化在類(lèi)的內(nèi)部創(chuàng)建實(shí)例提供獲取唯一實(shí)例的方法餓漢式根據(jù)上面的步驟,我們就可以輕松完成創(chuàng)建單例對(duì)象了。 前言 只有光頭才能變強(qiáng) 回顧前面: 給女朋友講解什么是代理模式 包裝模式就是這么簡(jiǎn)單啦 本來(lái)打算沒(méi)那么快更新的,這陣子在刷Spring的書(shū)籍。在看...

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

    摘要:?jiǎn)卫J疥P(guān)注的重點(diǎn)私有構(gòu)造器線程安全延遲加載序列化和反序列化安全反射攻擊安全相關(guān)設(shè)計(jì)模式單例模式和工廠模式工廠類(lèi)可以設(shè)計(jì)成單例模式。 0x01.定義與類(lèi)型 定義:保證一個(gè)類(lèi)僅有一個(gè)實(shí)例,并提供一個(gè)全局訪問(wèn)點(diǎn) 類(lèi)型:創(chuàng)建型 UML showImg(https://segmentfault.com/img/bVbtDJ2?w=402&h=268); 單例模式的基本要素 私有的構(gòu)造方...

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

    摘要:如果需要防范這種攻擊,請(qǐng)修改構(gòu)造函數(shù),使其在被要求創(chuàng)建第二個(gè)實(shí)例時(shí)拋出異常。單例模式與單一職責(zé)原則有沖突。源碼地址參考文獻(xiàn)設(shè)計(jì)模式之禪 定義 單例模式是一個(gè)比較簡(jiǎn)單的模式,其定義如下: 保證一個(gè)類(lèi)僅有一個(gè)實(shí)例,并提供一個(gè)訪問(wèn)它的全局訪問(wèn)點(diǎn)。 或者 Ensure a class has only one instance, and provide a global point of ac...

    k00baa 評(píng)論0 收藏0
  • 深入理解單例模式

    摘要:總結(jié)我們主要介紹到了以下幾種方式實(shí)現(xiàn)單例模式餓漢方式線程安全懶漢式非線程安全和關(guān)鍵字線程安全版本懶漢式雙重檢查加鎖版本枚舉方式參考設(shè)計(jì)模式中文版第二版設(shè)計(jì)模式深入理解單例模式我是一個(gè)以架構(gòu)師為年之內(nèi)目標(biāo)的小小白。 初遇設(shè)計(jì)模式在上個(gè)寒假,當(dāng)時(shí)把每個(gè)設(shè)計(jì)模式過(guò)了一遍,對(duì)設(shè)計(jì)模式有了一個(gè)最初級(jí)的了解。這個(gè)學(xué)期借了幾本設(shè)計(jì)模式的書(shū)籍看,聽(tīng)了老師的設(shè)計(jì)模式課,對(duì)設(shè)計(jì)模式算是有個(gè)更進(jìn)一步的認(rèn)識(shí)。...

    FuisonDesign 評(píng)論0 收藏0
  • Java基礎(chǔ)學(xué)習(xí)——多線程之單例設(shè)計(jì)模式(轉(zhuǎn))

    摘要:總之,選擇單例模式就是為了避免不一致?tīng)顟B(tài),避免政出多頭。二餓漢式單例餓漢式單例類(lèi)在類(lèi)初始化時(shí),已經(jīng)自行實(shí)例化靜態(tài)工廠方法餓漢式在類(lèi)創(chuàng)建的同時(shí)就已經(jīng)創(chuàng)建好一個(gè)靜態(tài)的對(duì)象供系統(tǒng)使用,以后不再改變,所以天生是線程安全的。 概念:  Java中單例模式是一種常見(jiàn)的設(shè)計(jì)模式,單例模式的寫(xiě)法有好幾種,這里主要介紹兩種:懶漢式單例、餓漢式單例?! 卫J接幸韵绿攸c(diǎn):  1、單例類(lèi)只能有一個(gè)實(shí)例?!?..

    dendoink 評(píng)論0 收藏0
  • 從未這么明白的設(shè)計(jì)模式(一):單例模式

    摘要:一般來(lái)說(shuō),這種單例實(shí)現(xiàn)有兩種思路,私有構(gòu)造器,枚舉。而這種方式又分了飽漢式,餓漢式。通過(guò)關(guān)鍵字防止指令重排序。什么是單例?為什么要用單例? 一個(gè)類(lèi)被設(shè)計(jì)出來(lái),就代表它表示具有某種行為(方法),屬性(成員變量),而一般情況下,當(dāng)我們想使用這個(gè)類(lèi)時(shí),會(huì)使用new關(guān)鍵字,這時(shí)候jvm會(huì)幫我們構(gòu)造一個(gè)該類(lèi)的實(shí)例。而我們知道,對(duì)于new這個(gè)關(guān)鍵字以及該實(shí)例,相對(duì)而言是比較耗費(fèi)資源的。所以如果我們能夠想...

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

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

0條評(píng)論

StonePanda

|高級(jí)講師

TA的文章

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