您好,登錄后才能下訂單哦!
本篇內(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í)用文章!
免責(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)容。