單例模式詳解
1勃蜘,編寫單例模式
餓漢式:不會存在線程安全的問題
public class Singleton1 {
????private Singleton1(){}
????private static Singleton1 singleton1 = new Singleton1();
????public static Singleton1 getInstance(){
????????return singleton1;
????}
}
懶漢式:會存在線程安全的問題,需要進行同步控制
以下的寫法存在線程安全闸英,經(jīng)過之前的學習,應(yīng)該很容易看出來吧
public class Singleton2 {
private Singleton2(){}
????private static Singleton2 singleton2;
????public static Singleton2 getInstance(){
????????if(singleton2 == null){
????????????singleton2 = new Singleton2();
????????}
????????return singleton2;
????}
}
驗證方式:
可以采用線程池的方式,創(chuàng)建多個線程去獲取實例對象纫谅,觀察獲取到的實例對象是否是同一個
2,解決懶漢式的線程安全問題
方法一:給方法加上synchronized即可
方法二:雙重檢測機制
public static Singleton2 getInstance(){
????if(singleton2 == null){
????????synchronized (Singleton2.class) {
????????????if(singleton2 == null){
????????????????singleton2 = new Singleton2();
????????????}
????????}
? ? ?}
? ? ?return singleton2;
}
3.2 指令重拍的問題(要注意一個指令重拍的問題溅固,但是無法演示付秕,只能YY。侍郭。询吴。掠河。。猛计。)
上述的雙重檢測機制看似解決了線程安全的問題唠摹,但是有一個重要的概念-指令重排,指令重排是指實際執(zhí)行時有滑,JVM編譯器并非一定按照我們預(yù)想的順序去執(zhí)行跃闹,會對指令的執(zhí)行順序進行調(diào)整,這個時候就可能會出現(xiàn)線程不安全的情況
來毛好,我們好好分析下:
singleton2 = new Singleton2();
會被編譯器編譯成如下JVM指令:
memory= allocate();//1.分配對象的內(nèi)存空間
ctorinstance(memory);//2望艺。初始化對象
singleton2 = memory;//3.設(shè)置singleton2指向剛分配的內(nèi)存空間
如果這個時候,經(jīng)過指令重排后肌访,執(zhí)行順序為1,3,2 那么結(jié)果會如何找默?
假設(shè),線程A執(zhí)行了1,3后吼驶,線程B搶到了CPU資源惩激,此時線程B對于if的判斷結(jié)果會是false,
但是實際返回的是一個沒有完成初始化的對象蟹演。
4风钻,解決指令重排的問題-volatile
private volatile static Singleton2 singleton2;
使用volatile就可以解決這個問題,可以保證執(zhí)行的指令順序始終按照我們預(yù)想的1,2,3來走
我們一次性把單例說完吧酒请,接下來的實現(xiàn)方式跟多線程是沒有關(guān)系的
5骡技,靜態(tài)內(nèi)部類實現(xiàn)單例模式
使用classLoader的加載機制來實現(xiàn)懶加載
public class Singleton3 {
????private static class Lazy{
????????private static final Singleton3 SINGLETON3 = new Singleton3();
????}
????private Singleton3(){}
????public static Singleton3 getInstance(){
????????return Lazy.SINGLETON3;
????}
}
解釋下,兩個關(guān)鍵點:
1羞反,外部無法直接訪問靜態(tài)內(nèi)部類
2布朦,SINGLETON3對象的初始化時機并不是在單例類加載的時候,而是外界調(diào)用getInstance方法的時候
所以綜上所述昼窗,可以保證線程安全
6是趴,終極大招---反射怎么破?
上述講了這么多的方式澄惊,但是通過反射可以將私有的構(gòu)造方法設(shè)置為可訪問唆途,然后就可以創(chuàng)建很多不同的對象了
那怎么辦?
終極大招掸驱,通過枚舉
public enum SingletonEnum {
????INSTANCE;
}
有了枚舉窘哈,JVM會阻止反射獲取枚舉的私有構(gòu)造方法
唯一的缺點就是:枚舉是立即加載的模式