1.餓漢式:靜態常量html
這種方法很是的簡單,由於單例的實例被聲明成static和final變量了,在第一次加載類到內存中時就會初始化,因此會建立實例自己是線程安全的。安全
public class Singleton1 { private final static Singleton1 instance = new Singleton1(); private Singleton1(){} public static Singleton1 getInstance(){ return instance; } }
它的缺點是否是一種懶加載,單例會在加載類後一開始就被初始化,即便客戶端沒有調用getInstance()方法。餓漢式的建立方式在一些場景中將沒法使用:好比Singleton實例的建立是依賴參數或者配置文件的,在getInstance()以前必須調用某個方法設置參數給它,那麼單例寫法就沒法使用了。
2.懶漢式:線程不安全多線程
public class Singleton3 { private static Singleton3 instance ; private Singleton3(){} public static Singleton3 getInstance(){ if(instance == null){ instance = new Singleton3(); } return instance; } }
這裏使用了懶加載模式,可是卻存在致命的問題。當多個線程並行調用getInstance()的時候,就會建立多個實例,即在多線程下不能正常工做。
3.懶漢式:線程安全ide
public class Singleton4 { private static Singleton4 instance; private Singleton4(){} public static synchronized Singleton4 getInstance(){ if(instance == null){ instance = new Singleton4(); } return instance; } }``` 雖然作到了線程安全,而且解決了多實例的問題,可是它並不高效。由於在任什麼時候候只能有一個線程調用getInstance()方法,可是同步操做只須要在第一次調用時才被須要,即第一次建立單例實例對象時。 4.懶漢式:靜態內部類
public class Singleton5 { 函數
private static class SingletonHandler{ private static final Singleton5 INSTANCE = new Singleton5(); } private Singleton5(){} public static Singleton5 getInstance(){ return SingletonHandler.INSTANCE; }
}```
這種寫法仍然使用JVM自己機制保證了線程安全問題;因爲SingletonHandler是私有的,除了getInstacne()以外沒有辦法訪問它,所以它是懶漢式的;同時讀取實例的時候不會進行同步,沒有性能缺陷,也不依賴JDK版本。
5.雙重檢驗鎖:性能
雙重檢驗模式,是一種使用同步塊加鎖的方法。又稱其爲雙重檢查鎖,由於會有兩次檢查instance == null,一次是在同步塊外,一次是在同步快內。爲何在同步塊內還要檢驗一次,由於可能會有多個線程一塊兒進入同步塊外的if,若是在同步塊內不進行二次檢驗的話就會生成多個實例了。優化
public class Singleton6 { private static Singleton6 instance; private Singleton6(){} public static Singleton6 getSingleton6(){ if(instance==null){ synchronized(Singleton6.class){ if(instance==null){ instance = new Singleton6(); } } } return instance; } }``` 其中,instance = new Singleton6()並不是是原子操做,事實上在JVM中這句話作了三件事: 1.給instance分配內存 2.調用Singleton6的構造函數來初始化成員變量 3.將instance對象指向分配的空間(執行完這一步instance就爲null) 可是在JVM的即時編譯器中存在指令重排序的優化,也就是說上面的第二步和第三步是不能保證順序的,最終執行的順序多是1-2-3或者是1-3-2。若是是後者,則在3執行完畢,2執行以前,被線程2搶佔了,這時instance已是非null了(但卻沒有初始化),因此線程2會直接返回instance,而後使用,而後會報錯。 ```public class Singleton6 { private volatile static Singleton6 instance; private Singleton6(){} public static Singleton6 getSingleton6(){ if(instance==null){ synchronized(Singleton6.class){ if(instance==null){ instance = new Singleton6(); } } } return instance; } }
有人認爲使用volatile的緣由是可見性,也就是能夠保證線程在本地不會存有instance副本,每次都是去主內存中讀取,可是實際上是不對的。使用volatile的主要緣由是其另外一個特性:禁止指令重排序優化。也就是說,在volatile變量的賦值操做後面會有一個內存屏障(生成的彙編代碼上),讀操做不會被重排序到內存屏障以前。好比上面的例子,取操做必須在執行完1-2-3以後或者1-3-2以後,不存在執行到1-3而後取到值的狀況。從[先行發生原則]的角度理解的話,就是對於一個volatile變量的寫操做都先行發生於後面對這個變量的讀操做。
注意:在Java5之前的版本使用了volatile的雙檢鎖仍是有問題的。其緣由是Java5之前的JMM(Java內存模型)是存在缺陷的,即時將變量聲明成volatile也不能避免重排序。線程
6.枚舉:code
public class Singleton7 { public enum EasySingleton{ INSTANCE; } }
咱們能夠經過EasySingleton.INSTANCE來訪問實例,這比調用getInstance()方法簡單多了。建立枚舉默認就是線程安全,並且還能防止反序列化致使從新建立新的對象。htm
更多幹貨教程地址:http://edu.51cto.com/lecturer/12509664.html和關注本帳號。