很是經典的例子,基本上對java有了解的同窗均可以寫出來,咱們的例子,可能存在一個BUG,這個BUG的緣由是,JMM出於對效率的考慮,是在happens-before原則內(out-of-order)亂序執行。java
public class LazySingleton { private int id; private static LazySingleton instance; private LazySingleton() { this.id= new Random().nextInt(200)+1; // (1) } public static LazySingleton getInstance() { if (instance == null) { // (2) synchronized(LazySingleton.class) { // (3) if (instance == null) { // (4) instance = new LazySingleton(); // (5) } } } return instance; // (6) } public int getId() { return id; // (7) } }
咱們初始化一個類,會產生多條彙編指令,然而總結下來,是執行下面三個事情:安全
1.給LazySingleton 的實例分配內存。
2.初始化LazySingleton 的構造器
3.將instance對象指向分配的內存空間(注意到這步instance就非null了)app
Java編譯器容許處理器亂序執行(out-of-order),咱們有多是1->2->3也有多是1->3->2。即咱們有可能在先返回instance實例,而後執行構造方法。dom
即:double-check-locking可能存在線程拿到一個沒有執行構造方法的對象。性能
線程A、B執行getInstance().getId()優化
在某一時刻,線程A執行到(5),而且初始化順序爲:1->3->2,當執行完將instance對象指向分配空間時。此時線程B執行(1),發現instance!=null,繼續執行,最後調用getId()返回0。此時切換到線程B對構造方法初始化。this
利用類第一次使用才加載,加載時同步的特性。
優勢是:官方推薦,能夠能夠保證明現懶漢模式。代碼少。
缺點是:第一次加載比較慢,並且多了一個類多了一個文件,總以爲不爽。線程
public class SingletonKerriganF { private static class SingletonHolder { static final SingletonKerriganF INSTANCE = new SingletonKerriganF(); } public static SingletonKerriganF getInstance() { return SingletonHolder.INSTANCE; } }
volatile禁止了指令重排序,因此確保了初始化順序必定是1->2->3,因此也就不存在拿到未初始化的對象引用的狀況。
優勢:保持了DCL,比較簡單
肯定:volatile這個關鍵字多少會帶來一些性能影響吧。code
public class Singleton(){ private volatile static Singleton singleton; private Sington(){}; public static Singleton getInstance(){ if(singleton == null){ synchronized (Singleton.class){ if(singleton == null){ singleton = new Singleton(); } } } return singleton; } }
經過一個temp,來肯定初始化結束後其餘線程才能得到引用。
同時注意,JIT可能對這一部分優化,咱們必須阻止JTL這部分的"優化"。對象
缺點是有點難理解,優勢是:能夠不用volatile關鍵字,又能夠用DLC,豈不妙哉。
public class Singleton { private static Singleton singleton; // 這類沒有volatile關鍵字 private Singleton() { } public static Singleton getInstance() { // 雙重檢查加鎖 if (singleton == null) { synchronized (Singleton.class) { // 延遲實例化,須要時才建立 if (singleton == null) { Singleton temp = null; try { temp = new Singleton(); } catch (Exception e) { } if (temp != null) //爲何要作這個看似無用的操做,由於這一步是爲了讓虛擬機執行到這一步的時會纔對singleton賦值,虛擬機執行到這裏的時候,必然已經完成類實例的初始化。因此這種寫法的DCL是安全的。因爲try的存在,虛擬機沒法優化temp是否爲null singleton = temp; } } } return singleton; } }