//之前學習的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,一個爲了自由安逸的將來而不斷前進的的程序員。
若是你以爲文章對你有一點點幫助,一個小小贊,即是對個人承認,若是有不足之處,也歡迎各位指正。函數