c語(yǔ)言除了做網(wǎng)站還能干什么廣州疫情升級(jí)
單例模式:保證某個(gè)類(lèi)在程序中只存在唯??份實(shí)例,而不會(huì)創(chuàng)建出多個(gè)實(shí)例,單例模式的類(lèi)一般是構(gòu)造器私有,通過(guò)一個(gè)方法返回唯一實(shí)例;
點(diǎn)這里查看線程安全的詳細(xì)講解;
常見(jiàn)的單例模式分為餓漢式和懶漢式
一、餓漢式
餓漢式會(huì)在類(lèi)加載的時(shí)候創(chuàng)建對(duì)象并初始化;
public class Singleton {private static final Singleton instance = new Singleton();private Singleton() {}public static Singleton getInstance() {return instance;}
}
以上是一個(gè)餓漢式實(shí)現(xiàn)的單例模式的典型代碼;由代碼可以看出, 在類(lèi)加載的時(shí)候?qū)ο笠呀?jīng)創(chuàng)建好了,也就是不管你需不需要使用,都已經(jīng)存在了,由 getInstance 方法返回這個(gè)對(duì)象,getInstance 方法直接 return,只涉及到讀操作,不涉及寫(xiě)操作,因此餓漢式是線程安全的;
二、懶漢式
懶漢式在類(lèi)加載的時(shí)候并不會(huì)直接創(chuàng)建出實(shí)例,而是在第一次使用的時(shí)候才會(huì)創(chuàng)建;
public class Singleton {private static Singleton instance = null;private Singleton() { }public static Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}
}
以上代碼是懶漢式實(shí)現(xiàn)的單例模式的典型代碼;其中,?剛開(kāi)始的時(shí)候,instance 對(duì)象并沒(méi)有實(shí)例化,在使用 getInstance 方法獲取該對(duì)象時(shí),會(huì)判斷該對(duì)象是否為空,為空才會(huì)初始化(也就是第一次使用的時(shí)候?yàn)榭?#xff09;,之后使用就會(huì)直接返回該對(duì)象;但是 getInstance 方法既存在讀操作,也存在寫(xiě)操作 instance = new Singleton(); ,那么在多線程的情況下,是否會(huì)存在線程安全問(wèn)題呢?答案是肯定的,試想如果兩個(gè)線程同時(shí)執(zhí)行到 if 判斷,此時(shí) instance 為空,兩個(gè)線程都會(huì)進(jìn)入 if 語(yǔ)句內(nèi),這樣兩個(gè)線程就會(huì)各自創(chuàng)建兩個(gè)對(duì)象并返回,這就違背了單例模式的初衷;
那么如何解決這個(gè)問(wèn)題呢?
優(yōu)化一
可以使用 synchronized 加鎖,由于兩個(gè)線程不應(yīng)該同時(shí)判斷出 instance == null,故可以對(duì)整個(gè) if 塊使用 synchronized 進(jìn)行加鎖;于是代碼就變?yōu)?#xff1a;
public class Singleton {private static Singleton instance = null;private Singleton() { }public static Singleton getInstance() {synchronized (Singleton.class) {if (instance == null) {instance = new Singleton();}}return instance;}
}
這樣一來(lái),在多線程的情況下,當(dāng)一個(gè)線程進(jìn)入到 if 塊內(nèi),其他線程就會(huì)阻塞等待,等待出了synchronized 塊之后,instance 實(shí)例也就 new 完了,其他線程再進(jìn)行判斷 instance 就不為 null 了,但是這樣一來(lái),之后的每次調(diào)用 getInstance 方法都會(huì)進(jìn)行加鎖,釋放鎖等操作,這樣系統(tǒng)開(kāi)銷(xiāo)就非常大,影響效率,而我們只需要在第一次創(chuàng)建實(shí)例的時(shí)候加鎖,因此即為了保證線程安全,又要保證效率,就得對(duì)上述代碼進(jìn)一步優(yōu)化;
優(yōu)化二
由于我們只需要在第一次創(chuàng)建實(shí)例的時(shí)候才加鎖,因此可以在 synchronized 外面再包裝一層 if 判斷,于是代碼進(jìn)一步變?yōu)?#xff1a;
public class Singleton {private static Singleton instance = null;private Singleton() { }public static Singleton getInstance() {if(instance == null) {synchronized (Singleton.class) {if (instance == null) {instance = new Singleton();}}}return instance;}
}
這樣一來(lái),既保證了線程安全,又不會(huì)非常影響效率,但是上述代碼還存在一個(gè)問(wèn)題:指令重排序問(wèn)題,在 new Singleton() 實(shí)例的時(shí)候,new 操作可以被拆分為三步:
1)申請(qǐng)內(nèi)存空間;
2)在內(nèi)存空間上構(gòu)造對(duì)象;
3)把內(nèi)存地址賦值給實(shí)例引用;
編譯器為了執(zhí)行效率,會(huì)優(yōu)化這三步的順序,但是 1 肯定是最先執(zhí)行的,因此 new 操作可能的執(zhí)行順序?yàn)?1 -> 2 -> 3,1 -> 3 -> 2,當(dāng)執(zhí)行順序?yàn)楹笳叩臅r(shí)候,假設(shè)有兩個(gè)線程 t1,t2,在 t1 執(zhí)行完 1, 3 還來(lái)不及執(zhí)行 2 的時(shí)候,此時(shí) t2 線程執(zhí)行到 if 判斷,此時(shí)由于 t1 線程執(zhí)行了步驟 3 ,所以 t2 判斷 if 不為 null,就直接返回 instance 對(duì)象了,但此時(shí) instance 指向的是一個(gè)還沒(méi)有初始化的非法對(duì)象,因此 t2 線程的后續(xù)代碼訪問(wèn) instance 里面的屬性和方法時(shí)就會(huì)出錯(cuò),為了避免這種情況,需要對(duì)上述代碼再進(jìn)行優(yōu)化;
優(yōu)化三
使用 volatile 關(guān)鍵字,告訴編譯器不要優(yōu)化指令重排序;
public class Singleton {private static volatile Singleton instance = null;private Singleton() { }public static Singleton getInstance() {if(instance == null) {synchronized (Singleton.class) {if (instance == null) {instance = new Singleton();}}}return instance;}
}
至此,線程安全的懶漢式就實(shí)現(xiàn)了;
?