創(chuàng)建型模式:對象實例化的模式氛赐,創(chuàng)建型模式用于解耦對象的實例化過程述么,即創(chuàng)建對象的同時隱藏創(chuàng)建邏輯。
1、使用場景
什么是單例模式呢贷祈,單例模式(Singleton)又叫單態(tài)模式趋急,它出現(xiàn)目的是為了保證一個類在系統(tǒng)中只有一個實例,并提供一個訪問它的全局訪問點势誊。從這點可以看出呜达,單例模式的出現(xiàn)是為了可以保證系統(tǒng)中一個類只有一個實例而且該實例又易于外界訪問,從而方便對實例個數(shù)的控制并節(jié)約系統(tǒng)資源而出現(xiàn)的解決方案键科。
使用單例模式當然是有原因闻丑,有好處的了。在下面幾個場景中適合使用單例模式:
1勋颖、有頻繁實例化然后銷毀的情況,也就是頻繁的 new 對象勋锤,可以考慮單例模式饭玲;
2、創(chuàng)建對象時耗時過多或者耗資源過多叁执,但又經(jīng)常用到的對象茄厘;
3、頻繁訪問 IO 資源的對象谈宛,例如數(shù)據(jù)庫連接池或訪問本地文件次哈;
4、有狀態(tài)的工具類對象吆录。
1窑滞、網(wǎng)站的計數(shù)器,通過單例模式可以很好實現(xiàn)
-
2恢筝、配置文件訪問類
項目中經(jīng)常需要一些環(huán)境相關(guān)的配置文件哀卫,比如短信通知相關(guān)的、郵件相關(guān)的撬槽。如果不用單例的話此改,每次都要 new 對象,每次都要重新讀一遍配置文件侄柔,很影響性能共啃,如果用單例模式,則只需要讀取一遍就好了暂题。
如果我們使用Spring框架移剪,可以用 @PropertySource 注解讀取properties文件,默認就是單例模式敢靡。public class SingleProperty { private static Properties prop; private static class SinglePropertyHolder{ private static final SingleProperty singleProperty = new SingleProperty(); } /** * config.properties 內(nèi)容是 test.name=kite */ private SingleProperty(){ System.out.println("構(gòu)造函數(shù)執(zhí)行"); prop = new Properties(); InputStream stream = SingleProperty.class.getClassLoader() .getResourceAsStream("config.properties"); try { prop.load(new InputStreamReader(stream, "utf-8")); } catch (IOException e) { e.printStackTrace(); } } public static SingleProperty getInstance(){ return SinglePropertyHolder.singleProperty; } public String getName(){ return prop.get("test.name").toString(); } public static void main(String[] args){ SingleProperty singleProperty = SingleProperty.getInstance(); System.out.println(singleProperty.getName()); } }
3挂滓、數(shù)據(jù)庫連接池、線程池
數(shù)據(jù)庫連接池的實現(xiàn),也包括線程池赶站。為什么要做池化幔虏,是因為新建連接很耗時,如果每次新任務(wù)來了贝椿,都新建連接想括,那對性能的影響實在太大。所以一般的做法是在一個應(yīng)用內(nèi)維護一個連接池烙博,這樣當任務(wù)進來時瑟蜈,如果有空閑連接,可以直接拿來用渣窜,省去了初始化的開銷铺根。用單例模式,正好可以實現(xiàn)一個應(yīng)用內(nèi)只有一個線程池的存在乔宿,所有需要連接的任務(wù)位迂,都要從這個連接池來獲取連接。如果不使用單例详瑞,那么應(yīng)用內(nèi)就會出現(xiàn)多個連接池掂林,那也就沒什么意義了。如果你使用 Spring 的話坝橡,并集成了例如 druid 或者 c3p0 泻帮,這些成熟開源的數(shù)據(jù)庫連接池,一般也都是默認以單例模式實現(xiàn)的计寇。
2锣杂、volatile雙重檢查鎖機制
public class SingleInstance {
private static volatile SingleInstance singleInstance;
private Singleton(){};
public static SingleInstance getSingleInstance(){ //1
//非空則跳過,因為只有首次初始化才有安全問題,保證了初始化之后饲常,線程不會阻塞蹲堂,提高了性能
if (singleInstance == null) { //2. 以一次檢查
synchronized(SingleInstance.class){ //3 加鎖
//voliatile能保證可見性,但不能保證原子性贝淤,加鎖保證線程并發(fā)情況下柒竞,也只有一個實例
if (singleInstance == null) { //4 第二次檢查
singleInstance = new SingleInstance();//5 創(chuàng)建實例
}
}
}
return singleInstance;
}
}
注:原子性,簡單的說播聪,就是多線程下朽基,簡單的賦值和讀取操作,具體請參考Java內(nèi)存模型
優(yōu)缺點:
- 雙重檢查鎖機制离陶,兼顧性能與安全稼虎,初始化之后,不會發(fā)生線程阻塞
這是單例模式優(yōu)先推薦的寫法
Java指令執(zhí)行的過程:
- 1.將變量從主存復(fù)制到線程的工作內(nèi)存中招刨;
- 2.然后進行讀操作霎俩;
- 3.有賦值指令時進行賦值操作;
- 4.將結(jié)果寫入主存中;
以上4步都是原子性的打却,但組合到一起杉适,多線程操作時不能保證整體原子性,這也就是線程并發(fā)安全問題的原因柳击。
volatile修飾詞作用:
- 1.某一線程對volatile修飾的變量進行修改后猿推,會強制將結(jié)果寫入主存,并使其它線程緩存行失效(失效后捌肴,讀操作不能從工作內(nèi)存中直接讀取蹬叭,從步驟1開始),即保證3和4指令執(zhí)行過程的整體原子性状知,并通知其它線程秽五。
- 2.禁止指令重排(代碼的編寫順序和指令執(zhí)行的順序不一致),一定程度上保證了有序性饥悴。
上述的singleInstance類變量假如沒有用volatile關(guān)鍵字修飾的筝蚕,則會導致這樣一個問題:
多線程情況下,在B線程執(zhí)行第5步時铺坞,A線程執(zhí)行到第4步,由于重排序的原因洲胖,B線程還沒有完成instance引用的對象的初始化济榨,但是A線程已經(jīng)讀取到singleInstance不為null,這時候就會導致空指針異常绿映。
第5步的代碼創(chuàng)建了一個對象擒滑,這一行代碼可以分解成3個操作:
memory = allocate(); // 1:分配對象的內(nèi)存空間
ctorInstance(memory); // 2:初始化對象
instance = memory; // 3:設(shè)置instance指向剛分配的內(nèi)存地址
根源在于代碼中的2和3之間,可能會被重排序叉弦。例如:
memory = allocate(); // 1:分配對象的內(nèi)存空間
instance = memory; // 3:設(shè)置instance指向剛分配的內(nèi)存地址
// 注意丐一,此時對象還沒有被初始化!
ctorInstance(memory); // 2:初始化對象
這在單線程環(huán)境下是沒有問題的淹冰,但在多線程環(huán)境下會出現(xiàn)問題库车,像上面的,B線程會看到一個還沒有被初始化的對象樱拴。
2和3的重排序不影響線程A的最終結(jié)果柠衍,但會導致線程B在第二步判斷出instance不為空,線程B接下來將訪問instance引用的對象晶乔。此時珍坊,線程B將會訪問到一個還未初始化的對象。
注:重排序是指編譯器和處理器為了優(yōu)化程序性能而對指令序列進行重新排序的一種手段正罢。
面試中有時會讓現(xiàn)場手寫一個簡單的單例模式阵漏,如果能寫出volatile方式,并且能把volatile機制講清楚,會加分不少履怯。
3回还、惡漢式
class SingleInstance2 {
private static final SingleInstance3 singleInstance3 = new SingleInstance3();
public static SingleInstance3 getSingleInstance3() {
return singleInstance3;
}
}
優(yōu)缺點:
- 優(yōu)點:既能保證并發(fā)安全,也能保證性能
- 缺點:類加載時就初始化虑乖,浪費內(nèi)存
4懦趋、加鎖懶漢式
class SingleInstance3 {
private static SingleInstance2 singleInstance2;
public static synchronized SingleInstance2 getSingleInstance2(){
if (singleInstance2 == null) {
singleInstance2 = new SingleInstance2();
}
return singleInstance2;
}
}
優(yōu)缺點:
- 缺點:犧牲了性能(初始化后,仍會發(fā)生線程阻塞問題)疹味,保證了并發(fā)安全