查漏補缺系列:寫單例時須要注意這三點!

1、不簡單的單例模式

//之前學習的low版單例
class LazySingleton{
    private static LazySingleton instance;
    private LazySingleton(){}
    public static LazySingleton getInstance() {
        if (instance==null){
            instance=new LazySingleton();
        }
        return instance;
    }
}

寫單例須要注意一下三點:
1)首先使用synchronized保證線程安全
2)使用double check機制來防止重複建立實例,就是先去判斷實例是否存在,若是不存在就執行同步代碼塊,那這樣的話,多個線程進來,會只有一個申請鎖成功,其餘阻塞。同步代碼塊裏面還有實例空判斷,這是爲了一個線程建立實例成功後,其餘線程進來就會判斷已經有了實例了就不會再去new對象。
3)避免jvm指令重排是用了volatile修飾符。編譯器(JIT),CPU 有可能對指令進行重排序,致使使用到還沒有初始化的實例,能夠經過添加volatile 關鍵字進行修飾,對於volatile 修飾的字段,能夠防止指令重排。java

//這裏使用的懶漢單例
class LazySingleton{
    private volatile static LazySingleton instance;
    //這裏注意不要漏了重寫構造空的私有構造函數,這裏容易忘
    private LazySingleton(){}
    public static LazySingleton getInstance() {
        if (instance==null){
            synchronized (LazySingleton.class){
                if (instance==null){
                    instance=new LazySingleton();
                }
            }

        }
        return instance;
    }
}



簡單擴展(加分項):

new Singleton()它並不是是一個原子操做,事實上在JVM大概作了如下3個事情:
  1. 給 singleton 分配內存
  2. 調用 Singleton 的構造函數來初始化成員變量,造成實例
  3. 將singleton對象指向分配的內存空間(執行完這步 singleton纔是非 null了)程序員

  咱們都知道,在JVM的JIT即時編譯器中存在指令重排序的優化。正常執行是1-2-3,但發生指令重排後有多是1-3-2。
  好比說有兩個線程,線程一先執行了步驟3,在執行步驟2以前,此時線程二進來了,判斷singleton實例時非null,注意此時singleton並未初始化,直接使用就報錯了。安全

這裏涉及了JVM的內存加載流程和指令重排的知識,有機會後邊再講下。jvm




hi~我是Mirror,一個爲了自由安逸的將來而不斷前進的的程序員。
若是你以爲文章對你有一點點幫助,一個小小贊,即是對個人承認,若是有不足之處,也歡迎各位指正。函數

相關文章
相關標籤/搜索