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