溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

java單例模式怎么定義

發(fā)布時(shí)間:2021-12-30 09:26:18 來(lái)源:億速云 閱讀:123 作者:iii 欄目:大數(shù)據(jù)

本篇內(nèi)容介紹了“java單例模式怎么定義”的有關(guān)知識(shí),在實(shí)際案例的操作過(guò)程中,不少人都會(huì)遇到這樣的困境,接下來(lái)就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!

一、單例模式定義:
單例模式確保某個(gè)類只有一個(gè)實(shí)例,而且自行實(shí)例化并向整個(gè)系統(tǒng)提供這個(gè)實(shí)例。在計(jì)算機(jī)系統(tǒng)中,線程池、緩存、日志對(duì)象、對(duì)話框、打印機(jī)、顯卡的驅(qū)動(dòng)程序?qū)ο蟪1辉O(shè)計(jì)成單例。這些應(yīng)用都或多或少具有資源管理器的功能。每臺(tái)計(jì)算機(jī)可以有若干個(gè)打印機(jī),但只能有一個(gè)Printer Spooler,以避免兩個(gè)打印作業(yè)同時(shí)輸出到打印機(jī)中。每臺(tái)計(jì)算機(jī)可以有若干通信端口,系統(tǒng)應(yīng)當(dāng)集中管理這些通信端口,以避免一個(gè)通信端口同時(shí)被兩個(gè)請(qǐng)求同時(shí)調(diào)用。總之,選擇單例模式就是為了避免不一致?tīng)顟B(tài),避免政出多頭。

1、經(jīng)典餓漢式:

public class Singleton {
    private final static Singleton INSTANCE = new Singleton();
    private Singleton(){}
    public static Singleton getInstance(){
        return INSTANCE;
    }
}
特點(diǎn):程序啟動(dòng)時(shí)加載,先加載類,再初始化靜態(tài)屬性,由于后面無(wú)法再對(duì)對(duì)象進(jìn)行修改,從而實(shí)現(xiàn)線程安全,效率相對(duì)高一些。占用內(nèi)存相對(duì)多一些。

缺點(diǎn):如果這個(gè)類特別龐大,初始化時(shí)將會(huì)特別緩慢,還有就是如果我們用不到這個(gè)類,它仍然會(huì)創(chuàng)建出來(lái),浪費(fèi)了資源。

2、經(jīng)典懶漢式:

public class Singleton {
    private static Singleton singleton;
    private Singleton() {}
    public static synchronized Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}
特點(diǎn):延時(shí)加載,節(jié)約了內(nèi)存。效率相對(duì)低一些。利用同步塊實(shí)現(xiàn)線程安全。

缺點(diǎn):synchronized關(guān)鍵字是一個(gè)重鎖(對(duì)象鎖),它會(huì)每次調(diào)用getInstance(),都要對(duì)對(duì)象上鎖,事實(shí)上,只有在第一次創(chuàng)建對(duì)象的時(shí)候需要加鎖,之后就不需要了。

3、懶漢式變種—雙重檢查結(jié)構(gòu)(不加volatile關(guān)鍵字修飾):

package cn.hzy.creationPattern.singleton;
 
public class Singleton3 {
    private static Singleton3 instance = null;
    private Singleton3(){    
    }
    public static Singleton3 getInstance(){
        if (instance == null) {
            synchronized (instance) {
            if (instance == null) {
                instance = new Singleton3();
        }
        }
        }
    return instance;
    }
}
特點(diǎn):屬于懶漢式的變種,上面懶漢式的特點(diǎn)都有,但是這里優(yōu)化了性能問(wèn)題,沒(méi)有給getInstance()方法加鎖,而是只給instance = new Singleton3();加鎖,也就是說(shuō)只在初始化的時(shí)候會(huì)加鎖,后面的訪問(wèn)因?yàn)閕nstance!=null,就不會(huì)加鎖。

缺點(diǎn):乍一看這種模式既沒(méi)有線程安全問(wèn)題,又保證了單例,貌似完美了,但是JVM在創(chuàng)建對(duì)象的時(shí)候有可能為了優(yōu)化性能而進(jìn)行指令重排,

看似簡(jiǎn)單的一句     instance = new Singleton3();   JVM在創(chuàng)建對(duì)象的時(shí)候會(huì)有三個(gè)步驟:

1、給Singleton3分配一個(gè)內(nèi)存空間

2、初始化Singleton3(也就是創(chuàng)建Singleton3對(duì)象)

3、將instance指向剛分配的內(nèi)存空間地址

但是有可能JVM為了編譯的優(yōu)化提高效率就有可能變成下面一種順序:

1、給Singleton3分配一個(gè)內(nèi)存空間

2、將instance指向剛分配的內(nèi)存空間地址

3、初始化Singleton3(也就是創(chuàng)建Singleton3對(duì)象)

其實(shí)這種情況在單線程情況下是毫無(wú)影響的,結(jié)果都一樣,但是如果在多線程情況下,就有可能導(dǎo)致錯(cuò)誤。

比如:A、B兩個(gè)線程訪問(wèn)getInstance()方法,A先進(jìn)入第一個(gè)if判斷,然后進(jìn)入synchronized塊,開(kāi)始初始化Singleton3,由于發(fā)生了指令重排,將instance指向剛分配的內(nèi)存空間地址(此時(shí)未創(chuàng)建Singleton3對(duì)象),在這個(gè)時(shí)候,B訪問(wèn)getInstance()方法,B進(jìn)入第一個(gè)if判斷,因?yàn)閕nstance已經(jīng)指向了一個(gè)存在的內(nèi)存空間地址,即instance!=null,此時(shí)直接返回instance(未初始化),然后再調(diào)用的時(shí)候如果A還沒(méi)有初始化完畢那么就會(huì)報(bào)空指針錯(cuò)誤。(概率很低)

解決方案:加上volatile關(guān)鍵字修飾,

volatile:

特性一:內(nèi)存可見(jiàn)性,即線程A對(duì)volatile變量的修改,其他線程獲取的volatile變量都是最新的。

特性二:可以禁止指令重排序。

修改如下:將   private static Singleton3 instance = null;   改為   private static volatile Singleton3 instance = null;

4、靜態(tài)內(nèi)部類:

package cn.hzy.creationPattern.singleton;
 
public class Singleton4 {
    private Singleton4() {}
    
    public static Singleton4 getInstance() {
        return SingletonFactory.instance;
    }
        
    private static class SingletonFactory {
    private static Singleton4 instance = new Singleton4();
    }
}
特點(diǎn):按特征也是屬于懶漢模式,因?yàn)橹粫?huì)在我們需要用的時(shí)候才會(huì)創(chuàng)建實(shí)例對(duì)象,這里通過(guò)構(gòu)造函數(shù)私有化,使用內(nèi)部類來(lái)維護(hù)單例的實(shí)現(xiàn),因?yàn)镴VM內(nèi)部的機(jī)制能夠保證當(dāng)一個(gè)類被加載的時(shí)候,這個(gè)類的加載過(guò)程是線程互斥的。這樣當(dāng)我們第一次調(diào)用getInstance的時(shí)候,JVM能夠幫我們保證instance只被創(chuàng)建一次, 并且會(huì)保證把賦值給instance的內(nèi)存初始化完畢,這樣我們就不用擔(dān)心Singleton3出現(xiàn)的問(wèn)題。同時(shí)該方法也只會(huì)在第一次調(diào)用的時(shí)候使用互斥機(jī)制,這樣就解決了低性能問(wèn)題。

缺點(diǎn):貌似這個(gè)就完美了,但是靜態(tài)內(nèi)部類也有著一個(gè)致命的缺點(diǎn),就是傳參的問(wèn)題,由于是靜態(tài)內(nèi)部類的形式去創(chuàng)建單例的,故外部無(wú)法傳遞參數(shù)進(jìn)去的。

5、枚舉:

public enum Singleton {
    INSTANCE;
    public void method() {
    }
}
直接調(diào)用SingleTon.INSTANCE就是單例。

特點(diǎn):創(chuàng)建枚舉默認(rèn)就是線程安全的

優(yōu)點(diǎn):簡(jiǎn)直不要太多,1、寫(xiě)法簡(jiǎn)單,對(duì)比上面的實(shí)例就能發(fā)現(xiàn)。2、可以防止反射攻擊。

針對(duì)上面的反射攻擊我這里簡(jiǎn)單說(shuō)一下:在上面的1、2、3、4種單例模式里面,如果不對(duì)構(gòu)造函數(shù)做一些安全處理,我們可以很輕松通過(guò)反射拿到構(gòu)造器并且創(chuàng)建不只一個(gè)實(shí)例對(duì)象,就不再是單例了。但是對(duì)于枚舉,即時(shí)你通過(guò)反射拿到構(gòu)造器,在創(chuàng)建對(duì)象實(shí)例的時(shí)候也會(huì)報(bào)錯(cuò),因?yàn)槊杜e是可以防止反射攻擊的。

怎么對(duì)構(gòu)造函數(shù)做一些安全處理?

可以立一個(gè)flag,在創(chuàng)建一個(gè)對(duì)象實(shí)例后,改變flag的值,通過(guò)判斷拋出異常。

比如:

private static boolean flag = false;
private Singleton (){
    synchronized (Singleton .class) {
        if(false == flag){
            flag = !flag;
        } else {
            throw new RuntimeException("單例模式正在被反射攻擊?。?!");
        }  
    }
}
通過(guò)在構(gòu)造函數(shù)里面增加一個(gè)判斷來(lái)保證不被反射攻擊。

“java單例模式怎么定義”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI