扣丁學(xué)堂Java培訓(xùn)之單例設(shè)計(jì)模式的線程同步
單例模式是最常用的設(shè)計(jì)模式之一,目的是保證一個(gè)類只有一個(gè)實(shí)例。
在項(xiàng)目中的作用:
1、解決因?yàn)轭l繁創(chuàng)建對(duì)象,導(dǎo)致資源消耗過(guò)大的問(wèn)題,如:數(shù)據(jù)庫(kù)的連接池,連接池用于創(chuàng)建數(shù)據(jù)庫(kù)連接,并對(duì)連接進(jìn)行回收使用,能減少數(shù)據(jù)庫(kù)連接的創(chuàng)建次數(shù),從而提高效率,但是連接池對(duì)象本身在項(xiàng)目中只需要一個(gè),就需要使用單例模式。類似的還有線程池等。
2、項(xiàng)目中能共享的工具類,如Java中的Runtime類能提供各種運(yùn)行環(huán)境系統(tǒng)參數(shù),它就被設(shè)計(jì)成了單例模式。
實(shí)現(xiàn)單例的過(guò)程:
1、要保證類只能創(chuàng)建一個(gè)對(duì)象,就必須隱藏類的構(gòu)造方法,所以要將構(gòu)造方法定義為私有的
2、在類中調(diào)用構(gòu)造方法創(chuàng)建對(duì)象,定義靜態(tài)方法用于返回對(duì)象。
下面這種單例模式屬于餓漢式單例模式,既一開(kāi)始就將對(duì)象實(shí)例化。這樣的做法會(huì)導(dǎo)致性能的降低,一般我們會(huì)采用延遲加載的方式,既需要對(duì)象時(shí)再實(shí)例化。
classHunger{ //靜態(tài)的實(shí)例 privatestaticHungerhunger=newHunger(); //隱藏構(gòu)造方法 privateHunger(){} //返回靜態(tài)實(shí)例 publicstaticHungergetInstance(){ returnhunger; } }
下面這種是懶漢式單例模式,既開(kāi)始不實(shí)例化對(duì)象,到需要該實(shí)例時(shí)再實(shí)例化,提高了運(yùn)行效率。
publicclassLazySingleton{ //靜態(tài)的實(shí)例 privatestaticLazySingletonsingle=null; //隱藏構(gòu)造方法 privateLazySingleton(){ System.out.println("創(chuàng)建LazySingleton對(duì)象"); } //返回靜態(tài)實(shí)例 publicstaticLazySingletongetInstance(){ if(single==null){ single=newLazySingleton(); } returnsingle; } }
線程安全問(wèn)題:懶漢單例模式在單線程環(huán)境沒(méi)有問(wèn)題,但在多線程環(huán)境下就會(huì)出現(xiàn)問(wèn)題。
執(zhí)行代碼,"創(chuàng)建LazySingleton對(duì)象"這句話會(huì)輸出多次,也就是創(chuàng)建了多個(gè)對(duì)象。
for(inti=0;i<100;i++){ newThread(newRunnable(){ @Override publicvoidrun(){ System.out.println(LazySingleton.getInstance()); }}).start(); }
分析原因:
假設(shè)線程1滿足getInstance方法中single==null條件后,準(zhǔn)備執(zhí)行創(chuàng)建對(duì)象的代碼,然后CPU被其它線程搶占,其它線程在getInstance方法中創(chuàng)建對(duì)象,然后線程1搶回CPU繼續(xù)執(zhí)行剛才未完成的創(chuàng)建對(duì)象代碼,這樣就創(chuàng)建了多個(gè)對(duì)象。
線程同步問(wèn)題的解決方法:
1、使用同步方法
publicstaticsynchronizedLazySingletongetInstance(){ if(single==null){ single=newLazySingleton(); } returnsingle; }
執(zhí)行剛才多線程的代碼后,我們發(fā)現(xiàn)可以解決同步問(wèn)題,但是同步方法存在的問(wèn)題是每個(gè)線程進(jìn)入后都會(huì)加鎖,執(zhí)行效率低。
2、使用同步代碼塊配合if使用
publicstaticLazySingletongetInstance(){ if(single==null){ synchronized(LazySingleton.class){ single=newLazySingleton(); } } returnsingle; }
同步塊和if語(yǔ)句配合使用,解決了每次都執(zhí)行上鎖導(dǎo)致的性能問(wèn)題,但是運(yùn)行代碼后我們會(huì)發(fā)現(xiàn),多線程同步的問(wèn)題還是可能出現(xiàn),原因是多個(gè)線程還是可能會(huì)同時(shí)進(jìn)入if語(yǔ)句。
3、使用雙重判斷
在同步塊中再添加一次if判斷就解決了上面的問(wèn)題,因?yàn)榫退愣鄠€(gè)線程同時(shí)進(jìn)入外層if語(yǔ)句,執(zhí)行同步塊還是要進(jìn)行一次判斷,這樣第一個(gè)線程創(chuàng)建對(duì)象后,后面的線程就不能再創(chuàng)建了。
publicstaticLazySingletongetInstance(){ //外層的if主要作用是判斷是否需要執(zhí)行同步塊,提高性能 if(single==null){ //靜態(tài)方法中將類作為鎖 synchronized(LazySingleton.class){ //判斷對(duì)象是否為空,為空就創(chuàng)建對(duì)象 if(single==null){ single=newLazySingleton(); } } } returnsingle; }
4、靜態(tài)內(nèi)部類
這種方法結(jié)合了餓漢式和懶漢式的特點(diǎn),如果不調(diào)用getInstance方法,靜態(tài)內(nèi)部類中的創(chuàng)建對(duì)象代碼不會(huì)執(zhí)行,調(diào)用getInstance后才會(huì)執(zhí)行,也就有了懶漢式延遲加載的效果,并且由于對(duì)象是直接創(chuàng)建的,還不存在線程同步問(wèn)題。
classMySingleton{ privateMySingleton(){ } privatestaticclassSingletonHelp{ staticMySingletoninstance=newMySingleton(); } publicstaticMySingletongetInstance(){ returnSingletonHelp.instance; } }
總結(jié):?jiǎn)卫J降膶?shí)現(xiàn)一般有餓漢式、懶漢式和靜態(tài)內(nèi)部類等方式,餓漢式創(chuàng)建對(duì)象的性能比較低但不存在線程同步問(wèn)題,懶漢式由于是延遲加載性能更高,但存在線程同步問(wèn)題,需要使用雙重判斷解決,靜態(tài)內(nèi)部類的方式也能實(shí)現(xiàn)單例模式但是代碼可讀性稍差。
如果項(xiàng)目對(duì)性能不敏感推薦使用餓漢式,如果是單線程環(huán)境可以使用一般的懶漢式,如果需要多線程則可以使用多重判斷的懶漢式或靜態(tài)內(nèi)部類實(shí)現(xiàn)。
以上就是關(guān)于Java開(kāi)發(fā)單例設(shè)計(jì)模式線程同步的詳細(xì)介紹,最后想要了解更多可以登錄扣丁學(xué)堂官網(wǎng)咨詢??鄱W(xué)堂是專業(yè)的Java培訓(xùn)機(jī)構(gòu),不僅有專業(yè)的老師和與時(shí)俱進(jìn)的課程體系,還有大量的Java視頻教程供學(xué)員觀看學(xué)習(xí),想要學(xué)好JavaEE的小伙伴抓緊時(shí)間行動(dòng)吧。扣丁學(xué)堂java技術(shù)交流群:487098661。微信號(hào):codingbb
*博客內(nèi)容為網(wǎng)友個(gè)人發(fā)布,僅代表博主個(gè)人觀點(diǎn),如有侵權(quán)請(qǐng)聯(lián)系工作人員刪除。