單例是一種設(shè)計(jì)模式苇侵,單例模式是在什么情況下產(chǎn)生的呢耕餐?
在Java中每個(gè)東西都看作對(duì)象店枣,若此時(shí)有如下情況,教室中的黑板(對(duì)象A)可以供教師(對(duì)象B)和學(xué)生(對(duì)象C)書(shū)寫(xiě)粪糙,此時(shí)黑板就是老師和學(xué)生的共享對(duì)象强霎,而單例模式就如此生成了。將黑板設(shè)計(jì)為單例蓉冈,則同時(shí)教師和學(xué)生都能共享城舞。
單例模式的特點(diǎn)
1、單例的類在系統(tǒng)里只有一個(gè)實(shí)例
2寞酿、單例的類能夠?qū)崿F(xiàn)自動(dòng)的實(shí)例化
3家夺、單例的類在對(duì)整個(gè)系統(tǒng)可見(jiàn)的,因此是public的
什么情況下將對(duì)象設(shè)置為單例
1伐弹、若某個(gè)對(duì)象被實(shí)例化很頻繁拉馋,然后銷毀的話,將此對(duì)象設(shè)置為單例
2惨好、若實(shí)例化此對(duì)象需要花費(fèi)較多時(shí)間煌茴,則將此對(duì)象設(shè)置為單例,增加效率
3日川、若此對(duì)象需要頻繁連接蔓腐,釋放數(shù)據(jù)庫(kù),文件的連接龄句,則將此對(duì)象設(shè)置為單例
4回论、有狀態(tài)的工具類(如線程池類散罕,此類要方便對(duì)池中的線程進(jìn)行控制)
餓漢模式
public class Test
{
private static final Test test = new Test();
private Test()
{
}
public static Test getInstance()
{
return test;
}
}
代碼中將Test的構(gòu)造函數(shù)設(shè)為private,則能夠保證其他對(duì)象不能通過(guò)默認(rèn)構(gòu)造函數(shù)初始化一個(gè)Test實(shí)例傀蓉,保證單例笨使。而getInstance()方法設(shè)置為靜態(tài)方法,即類方法僚害,則其他對(duì)象可以直接調(diào)動(dòng)此類方法獲得Test實(shí)例。從代碼中我們能發(fā)現(xiàn)繁调,懶漢模式的單例中萨蚕,Test實(shí)例在編譯時(shí)就生成。其優(yōu)點(diǎn)是蹄胰,在程序運(yùn)行時(shí)不用再對(duì)其進(jìn)行實(shí)例化岳遥,它已經(jīng)被初始化好保存在靜態(tài)內(nèi)存之中,因此效率高裕寨;但同時(shí)它就面臨一個(gè)缺點(diǎn)浩蓉,若有這樣的一種情況,程序調(diào)用中沒(méi)用到Test實(shí)例宾袜,但Test實(shí)例會(huì)一直存在內(nèi)存中捻艳,占用資源。
懶漢模式
從字面意義上我們就能體會(huì)到庆猫,懶漢模式就是在程序真正用到Test實(shí)例的時(shí)候再去初始化它认轨。
public class Test
{
private static Test test = null;
private Test()
{
}
public Test getInstance()
{
if(null == test)
{
test = new Test();
}
return test;
}
}
同餓漢模式一樣,將構(gòu)造函數(shù)設(shè)為private防止其他對(duì)象調(diào)用默認(rèn)構(gòu)造函數(shù)創(chuàng)建Test實(shí)例月培。但與餓漢模式不同的是嘁字,test變量設(shè)置為在一開(kāi)始并未對(duì)其初始化,而是在調(diào)用getInstance()后進(jìn)行判斷杉畜,若test為空才對(duì)其進(jìn)行初始化纪蜒,保證了test實(shí)例初始化時(shí)間在實(shí)際調(diào)用的時(shí)候才發(fā)生。
但懶漢模式面臨一個(gè)問(wèn)題此叠,在多線程情況下纯续,若線程A和線程B幾乎同時(shí)調(diào)用test,此時(shí)A剛好在執(zhí)行new Test()的時(shí)候B正好運(yùn)行到判斷null == test這條語(yǔ)句拌蜘,此時(shí)就會(huì)產(chǎn)生出兩個(gè)test實(shí)例杆烁,因此多線程情況下,并不能保證單例简卧。
為了改進(jìn)懶漢模式的線程不安全問(wèn)題兔魂,引入了Synchronizend關(guān)鍵字,保證線程的安全對(duì)代碼進(jìn)行了如下改正
public class Test
{
private static Test test = null;
private Test()
{
}
public Test getInstance()
{
if(null == test)
{
Synchronized(test.class)
{
if(null == test)
{
test = new Test();
}
}
}
return test;
}
}
此時(shí)為A B兩線程同時(shí)調(diào)用Test實(shí)例的時(shí)候举娩,若A先進(jìn)入test為空的判斷則會(huì)對(duì)代碼進(jìn)行加鎖析校,只有在Test實(shí)例化成功后再釋放鎖构罗,此時(shí)B再運(yùn)行。那為啥此段代碼中加上兩重判斷呢智玻?若存在這樣的情況遂唧,當(dāng)A釋放鎖后B獲得鎖,若此時(shí)不加以判斷吊奢,B仍然認(rèn)為test為空(因?yàn)榕锌照Z(yǔ)句在鎖前面)此時(shí)它仍然會(huì)繼續(xù)創(chuàng)建一個(gè)test實(shí)例盖彭,但此時(shí)若再加上一個(gè)判空,則能保證當(dāng)B獲得鎖后Test不會(huì)再被實(shí)例化页滚。