知識(shí)
不管是網(wǎng)站,軟件還是小程序,都要直接或間接能為您產(chǎn)生價(jià)值,我們?cè)谧非笃湟曈X表現(xiàn)的同時(shí),更側(cè)重于功能的便捷,營(yíng)銷的便利,運(yùn)營(yíng)的高效,讓網(wǎng)站成為營(yíng)銷工具,讓軟件能切實(shí)提升企業(yè)內(nèi)部管理水平和效率。優(yōu)秀的程序?yàn)楹笃谏?jí)提供便捷的支持!
設(shè)計(jì)模式(一):“懶漢式”與“餓漢式”單例模式
發(fā)表時(shí)間:2019-7-5
發(fā)布人:葵宇科技
瀏覽次數(shù):50
何為單例模式?單例模式即一個(gè)類只有一個(gè)實(shí)例并且該類有提供一個(gè)全局訪問點(diǎn)。我們常常希望某個(gè)對(duì)象實(shí)例只有一個(gè),不想要頻繁地創(chuàng)建和銷毀對(duì)象,浪費(fèi)系統(tǒng)資源,這時(shí)候我們就要使用單例模式來獲取類的實(shí)例。那怎么才能保證一個(gè)類是單例的呢?
我們可以先讓一個(gè)類的構(gòu)造方法給私有化,這樣外部就不能通過new來創(chuàng)建類的對(duì)象,然后使用靜態(tài)變量instance將實(shí)例保存起來,當(dāng)需要使用該類的實(shí)例時(shí)只要調(diào)用getInatance()方法就可以得到該類的實(shí)例了。代碼如下:
class SingleObject{
private static SingleObject instance;
private SingleObject(){
}
public static SingleObject getInstance(){
if(instance == null){
instance = new SingleObject();
return instance;
}
}
}
但這樣就足夠了嗎?如果只有單個(gè)線程運(yùn)行程序的情況下,確實(shí)只會(huì)返回一個(gè)實(shí)例,但如果在多線程情況下,還是可能會(huì)產(chǎn)生多個(gè)實(shí)例,為什么?
舉個(gè)例子,如果線程a在執(zhí)行完getInstance方法里的if(instance == null)這句代碼后就因?yàn)闀r(shí)間片用完變成阻塞狀態(tài),然后線程b執(zhí)行g(shù)etInstance方法時(shí),此時(shí)instance實(shí)例還沒有被new出來,所以線程b就執(zhí)行if代碼塊里的代碼創(chuàng)建了一個(gè)SingleObject對(duì)象,并賦值給instance,然后結(jié)束。線程a被喚醒后,繼續(xù)執(zhí)行之前的代碼,也就是if代碼塊里的代碼,這時(shí)就會(huì)產(chǎn)生SingleObject類的多個(gè)實(shí)例。
為了解決多線程情況下可能會(huì)產(chǎn)生多個(gè)實(shí)例的問題,我們可以使用synchronized關(guān)鍵字來給產(chǎn)生instance實(shí)例的代碼塊“加鎖”解決這個(gè)問題:
class SingleObject{
private static SingleObject instance;
public static SingleObject getInstance(){
synchronized (instance){
if(instance == null){
instance = new SingleObject();
return instance;
}
}
return instance;
}
}
通過synchronized關(guān)鍵字給創(chuàng)建實(shí)例的代碼塊“加鎖”,同一時(shí)刻下,只有一個(gè)線程能夠執(zhí)行這個(gè)代碼塊里的代碼,先執(zhí)行這個(gè)代碼塊的線程發(fā)現(xiàn)沒有instance實(shí)例后會(huì)創(chuàng)建instance實(shí)例,后面執(zhí)行的線程會(huì)因?yàn)樵搶?shí)例已經(jīng)存在而不會(huì)再去創(chuàng)建instance實(shí)例。
不過這并不是完美的解決方案,只要是鎖,必然有性能損耗問題。而且對(duì)于上面的代碼,其實(shí)我們只需要在線程第一次訪問時(shí)加鎖即可,之后并不需要鎖,鎖給我們帶來了系統(tǒng)資源浪費(fèi)。所以我們可以試著優(yōu)化一下,如下面代碼:
public class SingleObject {
private static SingleObject instance;
private SingleObject(){
}
public static SingleObject getInstance(){
if (instance == null){
synchronized (instance){
if(instance == null){
instance = new SingleObject();
return instance;
}
}
}
return instance;
}
}
我們可以在同步代碼塊外再加一個(gè)判斷對(duì)象實(shí)例是否存的if語句,這樣大部分線程執(zhí)行完第一個(gè)if判斷后就不用進(jìn)入同步代碼塊中,而是直接獲得instance對(duì)象,避免了加鎖解鎖的資源消耗。
經(jīng)過我們使用雙重判斷的優(yōu)化,看似已經(jīng)完美解決了多線程情況下出現(xiàn)多個(gè)實(shí)例的問題,其實(shí)還留有一個(gè)隱患,這個(gè)隱患在第二個(gè)if判斷的代碼塊里的instance = new SingleObject()這一句代碼上,這句代碼在JVM中不是一個(gè)原子操作,而是先有一條字節(jié)碼指令來調(diào)用SingleObject對(duì)象的構(gòu)造方法,然后下一條字節(jié)碼指令將SingleObject對(duì)象的地址賦值給instance引用,但由于JVM的指令重排序優(yōu)化,這兩條指令的執(zhí)行順序會(huì)發(fā)生顛倒,即先將SingleObject對(duì)象的地址賦值給instance引用,再調(diào)用SingleObject對(duì)象的構(gòu)造方法,這樣會(huì)出現(xiàn)的情況就是,第一個(gè)線程在執(zhí)行將SingleObject對(duì)象的地址賦值給instance引用賦值這條指令后,由于cpu時(shí)間片用完,而沒有調(diào)用SingleObject對(duì)象的構(gòu)造方法后就阻塞,接著第二個(gè)線程在執(zhí)行第一個(gè)if判斷時(shí),此時(shí)的instance已經(jīng)有值,直接return instance,但是此時(shí)的instance引用指向的對(duì)象并沒有調(diào)用構(gòu)造方法,所以是個(gè)空對(duì)象,這樣就出現(xiàn)了問題。
那該如何解決這個(gè)問題呢,我們可以使用volatile關(guān)鍵字來修飾instance變量,代碼如下:
public class SingleObject {
private static volatile SingleObject instance;
private SingleObject(){
}
public static SingleObject getInstance(){
if (instance == null){
synchronized (instance){
if(instance == null){
instance = new SingleObject();
return instance;
}
}
}
return instance;
}
}
volatile關(guān)鍵字可以禁用被修飾變量的讀寫操作指令的重排序,所以instance = new SingleObject()這一句代碼將會(huì)按照先調(diào)用構(gòu)造方法、再賦值引用的順序執(zhí)行,這樣的話,當(dāng)?shù)谝粋€(gè)線程進(jìn)入阻塞狀態(tài)時(shí),第二個(gè)線程在第一個(gè)if判斷時(shí)會(huì)因?yàn)?strong>instance的值為空而進(jìn)入第一個(gè)if代碼塊中,但if代碼塊中的代碼被synchronized給鎖住,而此時(shí)鎖又被第一個(gè)線程擁有,所以第二個(gè)線程會(huì)進(jìn)入鎖對(duì)象的等待隊(duì)列中等待。而當(dāng)?shù)谝粋€(gè)線程再次獲得時(shí)間片時(shí),它會(huì)繼續(xù)實(shí)例化SingleObject對(duì)象并賦值給instance引用,然后結(jié)束并釋放鎖。而第二個(gè)線程被喚醒后拿到鎖執(zhí)行第二個(gè)if判斷,因?yàn)閕nstance已經(jīng)有值,所以直接結(jié)束并釋放鎖。這樣就完美地解決了多線程模式下可能會(huì)產(chǎn)生多個(gè)實(shí)例的問題。
上面創(chuàng)建單例對(duì)象的方式都是在 getInstance() 方法中創(chuàng)建實(shí)例,也就是說在要調(diào)用的時(shí)候才創(chuàng)建實(shí)例,這種方式被稱為 “ 懶漢式 ” 我們也可以使用“餓漢式”單例模式 ,在類加載時(shí)就創(chuàng)建好了實(shí)例,代碼如下:
class SingleObject{
private static final SingleObject instance = new SingleObject();
private SingleObject(){
}
public static SingleObject getInstance(){
return instance;
}
}
“餓漢式”的單例模式就是在單例類里面去定義和實(shí)例化一個(gè)靜態(tài)的單例類對(duì)象,這樣在單例類被加載時(shí)靜態(tài)變量的單例對(duì)象就會(huì)被創(chuàng)建出來,不用去擔(dān)心線程安全等問題。但是,這樣做的壞處是不管你這個(gè)項(xiàng)目用不用到這個(gè)單例類對(duì)象,該對(duì)象都會(huì)被創(chuàng)建出來,可能會(huì)造成資源浪費(fèi)。
相關(guān)案例查看更多
相關(guān)閱讀
- 云南網(wǎng)站建設(shè)服務(wù)
- 北京小程序開發(fā)
- web開發(fā)
- 昆明軟件公司
- 云南網(wǎng)站維護(hù)
- 云南網(wǎng)站建設(shè)公司排名
- 網(wǎng)站搭建
- 報(bào)廢車回收管理軟件
- 昆明小程序公司
- 汽車報(bào)廢回收
- 智慧農(nóng)貿(mào)市場(chǎng)
- 昆明網(wǎng)站建設(shè)公司
- 云南小程序開發(fā)公司哪家好
- 小程序公司
- 報(bào)廢車拆解管理系統(tǒng)
- 汽車回收系統(tǒng)
- 企業(yè)網(wǎng)站
- 網(wǎng)絡(luò)公司報(bào)價(jià)
- 云南建設(shè)廳網(wǎng)站
- 做小程序被騙
- 網(wǎng)站建設(shè)首頁
- 云南網(wǎng)站建設(shè) 網(wǎng)絡(luò)服務(wù)
- 云南網(wǎng)頁制作
- 海南小程序制作公司
- 網(wǎng)站建設(shè)電話
- 云南網(wǎng)站建設(shè)制作
- 用戶登錄
- 網(wǎng)站制作
- 電商網(wǎng)站建設(shè)
- 百度小程序開發(fā)公司