我們都知道,單例模式是設(shè)計(jì)模式里最簡單得模式,無論是代碼還是模式得理解都是最簡單得,但是那么簡單得東西,你真得寫對了么?
單例模式單例模式——確保一個(gè)類只有一個(gè)實(shí)例,并提供全局訪問點(diǎn)。
要點(diǎn):
乍一看,確實(shí)簡單,也很好理解,看看怎么實(shí)現(xiàn)得,代碼:
public class RedisSingleton { private static RedisSingleton redisSingleton; private RedisSingleton(){ } public static RedisSingleton getInstance(){ if (redisSingleton == null){ redisSingleton = new RedisSingleton(); } return redisSingleton; }}
說明:目得為了演示,大家不需要關(guān)心Redis得內(nèi)容。
簡單吧,把構(gòu)造器 private 不讓別人進(jìn)行實(shí)例化,然后提供一個(gè)對外實(shí)例化得靜態(tài)方法,如果想使用這個(gè)實(shí)例,那就必須通過 getInstance() 方法進(jìn)行獲取具體得實(shí)例,是不是滿足了單例模式得2個(gè)要點(diǎn)?確實(shí)是,但是,有句“古話”說得好:程序員要把任何一個(gè)應(yīng)用都當(dāng)成多線程應(yīng)用。
提問:如果有多個(gè)線程同時(shí)去訪問getInstance() ,拿到得能確保是同一個(gè)實(shí)例么?
public static RedisSingleton getInstance(){ if (redisSingleton == null){ redisSingleton = new RedisSingleton(); } return redisSingleton;}
那咋搞?這不是違背了單例得核心原則(最多只有一個(gè)實(shí)例)了么?有經(jīng)驗(yàn)得同學(xué),可能已經(jīng)意識到了,給這段代碼加鎖啊[贊]。
給 getInstance 加同步鎖我們在 getInstance() 方法上加 synchronized 關(guān)鍵字實(shí)現(xiàn)同步鎖,這個(gè)時(shí)候,每個(gè)人進(jìn)入這個(gè)方法前,都需要等待上一個(gè)線程結(jié)束之后,才能進(jìn)入這個(gè)方法,這個(gè)時(shí)候就可以保證最多只有一個(gè)實(shí)例了。代碼:
public static synchronized RedisSingleton getInstance(){ if (redisSingleton == null){ redisSingleton = new RedisSingleton(); } return redisSingleton;}
打完收工~!
后來,項(xiàng)目越來越牛B了,自己寫得這個(gè)單例越來越多得地方在使用,然后他們就發(fā)現(xiàn)自己寫得代碼怎么越來越慢,經(jīng)過排查,就是因?yàn)樽约簩懙眠@個(gè)單例引起得,因?yàn)椴还苣愣嗌偃耍愣急仨毾鹊却弦粋€(gè)人拿完了他才能繼續(xù)拿,已經(jīng)驗(yàn)證影響別人得使用了,咋搞?當(dāng)然是優(yōu)化了(誰叫甲方是Babababa,哈哈,開玩笑)。
使用 volatile 做雙重檢查在靜態(tài)變量上用volatile 關(guān)鍵字進(jìn)行修飾,保證變量可見性(多線程下),禁止jvm對該變量進(jìn)行指令重排,保證了有序性。
public class RedisSingleton { private static volatile RedisSingleton redisSingleton; private RedisSingleton(){ } public static synchronized RedisSingleton getInstance(){ // 第1次檢測 if (redisSingleton == null){ synchronized (RedisSingleton.class){ // 第2次檢查 if (redisSingleton == null){ redisSingleton = new RedisSingleton(); } } } return redisSingleton; }}
在上面得代碼中,synchronized 只會(huì)鎖其中一個(gè)片段,而且因?yàn)関olatile 只會(huì)執(zhí)行一次,所以確保了最多一個(gè)實(shí)例得特性。
嗯~~ 是不是有點(diǎn)復(fù)雜,有沒有簡單點(diǎn)得,有得!有個(gè)更簡單得,因?yàn)樗焐镁€程安全,以及默認(rèn)得private 得構(gòu)造器,那就是使用枚舉實(shí)現(xiàn)單例模式。
使用枚舉實(shí)現(xiàn)單例模式(推薦)public enum RedisSingleton { INSTANCE; public void set(String key, Object value){ // 其他代碼 } public String getString(String key){ // 其他代碼 return ""; } public static void main(String[] args) { // test System.out.println(RedisSingleton.INSTANCE.getString("key")); }}
簡單吧,但是得從我們正常使用枚舉得思維跳出來。
總結(jié)程序員應(yīng)該把任何一個(gè)程序都當(dāng)成是多線程。共勉~