1.餓漢式:靜態(tài)常量這種方法非常的簡單帮掉,因為單例的實例被聲明成static和final變量了单匣,在第一次加載類到內(nèi)存中時就會初始化团南,所以會創(chuàng)建實例本身是線程安全的宗雇。[java] view plain copypublic class Singleton1 {
private final static Singleton1 instance = new Singleton1();
private Singleton1(){}
public static Singleton1 getInstance(){
return instance;
}
}它的缺點是不是一種懶加載钳恕,單例會在加載類后一開始就被初始化别伏,即使客戶端沒有調(diào)用getInstance()方法。餓漢式的創(chuàng)建方式在一些場景中將無法使用:比如Singleton實例的創(chuàng)建是依賴參數(shù)或者配置文件的忧额,在getInstance()之前必須調(diào)用某個方法設(shè)置參數(shù)給它厘肮,那么單例寫法就無法使用了。2.懶漢式:線程不安全[java] view plain copypublic class Singleton3 {
private static Singleton3 instance ;
private Singleton3(){}
public? static Singleton3 getInstance(){
if(instance == null){
instance = new Singleton3();
}
return instance;
}
}這里使用了懶加載模式睦番,但是卻存在致命的問題类茂。當多個線程并行調(diào)用getInstance()的時候,就會創(chuàng)建多個實例托嚣,即在多線程下不能正常工作巩检。3.懶漢式:線程安全[java] view plain copypublic class Singleton4 {
private static Singleton4 instance;
private Singleton4(){}
public static synchronized Singleton4 getInstance(){
if(instance == null){
instance = new Singleton4();
}
return instance;
}
}雖然做到了線程安全,并且解決了多實例的問題示启,但是它并不高效兢哭。因為在任何時候只能有一個線程調(diào)用getInstance()方法,但是同步操作只需要在第一次調(diào)用時才被需要丑搔,即第一次創(chuàng)建單例實例對象時厦瓢。4.懶漢式:靜態(tài)內(nèi)部類[java] view plain copypublic 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.雙重檢驗鎖:雙重檢驗?zāi)J剑且环N使用同步塊加鎖的方法夹姥。又稱其為雙重檢查鎖杉武,因為會有兩次檢查instance == null,一次是在同步塊外辙售,一次是在同步快內(nèi)轻抱。為什么在同步塊內(nèi)還要檢驗一次,因為可能會有多個線程一起進入同步塊外的if旦部,如果在同步塊內(nèi)不進行二次檢驗的話就會生成多個實例了祈搜。[java] view plain copypublic 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分配內(nèi)存2.調(diào)用Singleton6的構(gòu)造函數(shù)來初始化成員變量3.將instance對象指向分配的空間(執(zhí)行完這一步instance就為null)但是在JVM的即時編譯器中存在指令重排序的優(yōu)化容燕,也就是說上面的第二步和第三步是不能保證順序的,最終執(zhí)行的順序可能是1-2-3或者是1-3-2婚度。如果是后者蘸秘,則在3執(zhí)行完畢,2執(zhí)行之前蝗茁,被線程2搶占了醋虏,這時instance已經(jīng)是非null了(但卻沒有初始化),所以線程2會直接返回instance评甜,然后使用灰粮,然后會報錯。[java] view plain copypublic 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副本,每次都是去主內(nèi)存中讀取熔脂,但是其實是不對的佩研。使用volatile的主要原因是其另一個特性:禁止指令重排序優(yōu)化。也就是說霞揉,在volatile變量的賦值操作后面會有一個內(nèi)存屏障(生成的匯編代碼上)旬薯,讀操作不會被重排序到內(nèi)存屏障之前。比如上面的例子适秩,取操作必須在執(zhí)行完1-2-3之后或者1-3-2之后绊序,不存在執(zhí)行到1-3然后取到值的情況硕舆。從[先行發(fā)生原則]的角度理解的話,就是對于一個volatile變量的寫操作都先行發(fā)生于后面對這個變量的讀操作骤公。注意:在Java5以前的版本使用了volatile的雙檢鎖還是有問題的抚官。其原因是Java5以前的JMM(Java內(nèi)存模型)是存在缺陷的,即時將變量聲明成volatile也不能避免重排序阶捆。6.枚舉:[java] view plain copypublic class Singleton7 {
public enum EasySingleton{
INSTANCE;
}
}我們可以通過EasySingleton.INSTANCE來訪問實例凌节,這比調(diào)用getInstance()方法簡單多了。創(chuàng)建枚舉默認就是線程安全洒试,而且還能防止反序列化導致重新創(chuàng)建新的對象倍奢。