1.餓漢式:靜態(tài)常量這種方法非常的簡(jiǎn)單,因?yàn)閱卫膶?shí)例被聲明成static和final變量了,在第一次加載類到內(nèi)存中時(shí)就會(huì)初始化,所以會(huì)創(chuàng)建實(shí)例本身是線程安全的。[java] view plain copypublic class Singleton1 {
private final static Singleton1 instance = new Singleton1();
private Singleton1(){}
public static Singleton1 getInstance(){
return instance;
}
}它的缺點(diǎn)是不是一種懶加載,單例會(huì)在加載類后一開始就被初始化,即使客戶端沒有調(diào)用getInstance()方法。餓漢式的創(chuàng)建方式在一些場(chǎng)景中將無法使用:比如Singleton實(shí)例的創(chuàng)建是依賴參數(shù)或者配置文件的,在getInstance()之前必須調(diào)用某個(gè)方法設(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;
}
}這里使用了懶加載模式,但是卻存在致命的問題。當(dāng)多個(gè)線程并行調(diào)用getInstance()的時(shí)候,就會(huì)創(chuàng)建多個(gè)實(shí)例,即在多線程下不能正常工作。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;
}
}雖然做到了線程安全,并且解決了多實(shí)例的問題,但是它并不高效。因?yàn)樵谌魏螘r(shí)候只能有一個(gè)線程調(diào)用getInstance()方法,但是同步操作只需要在第一次調(diào)用時(shí)才被需要,即第一次創(chuàng)建單例實(shí)例對(duì)象時(shí)。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本身機(jī)制保證了線程安全問題;由于SingletonHandler是私有的,除了getInstacne()之外沒有辦法訪問它,因此它是懶漢式的;同時(shí)讀取實(shí)例的時(shí)候不會(huì)進(jìn)行同步,沒有性能缺陷,也不依賴JDK版本。5.雙重檢驗(yàn)鎖:雙重檢驗(yàn)?zāi)J剑且环N使用同步塊加鎖的方法。又稱其為雙重檢查鎖,因?yàn)闀?huì)有兩次檢查instance == null,一次是在同步塊外,一次是在同步快內(nèi)。為什么在同步塊內(nèi)還要檢驗(yàn)一次,因?yàn)榭赡軙?huì)有多個(gè)線程一起進(jìn)入同步塊外的if,如果在同步塊內(nèi)不進(jìn)行二次檢驗(yàn)的話就會(huì)生成多個(gè)實(shí)例了。[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()并非是原子操作,事實(shí)上在JVM中這句話做了三件事:1.給instance分配內(nèi)存2.調(diào)用Singleton6的構(gòu)造函數(shù)來初始化成員變量3.將instance對(duì)象指向分配的空間(執(zhí)行完這一步instance就為null)但是在JVM的即時(shí)編譯器中存在指令重排序的優(yōu)化,也就是說上面的第二步和第三步是不能保證順序的,最終執(zhí)行的順序可能是1-2-3或者是1-3-2。如果是后者,則在3執(zhí)行完畢,2執(zhí)行之前,被線程2搶占了,這時(shí)instance已經(jīng)是非null了(但卻沒有初始化),所以線程2會(huì)直接返回instance,然后使用,然后會(huì)報(bào)錯(cuò)。[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;
}
}有人認(rèn)為使用volatile的原因是可見性,也就是可以保證線程在本地不會(huì)存有instance副本,每次都是去主內(nèi)存中讀取,但是其實(shí)是不對(duì)的。使用volatile的主要原因是其另一個(gè)特性:禁止指令重排序優(yōu)化。也就是說,在volatile變量的賦值操作后面會(huì)有一個(gè)內(nèi)存屏障(生成的匯編代碼上),讀操作不會(huì)被重排序到內(nèi)存屏障之前。比如上面的例子,取操作必須在執(zhí)行完1-2-3之后或者1-3-2之后,不存在執(zhí)行到1-3然后取到值的情況。從[先行發(fā)生原則]的角度理解的話,就是對(duì)于一個(gè)volatile變量的寫操作都先行發(fā)生于后面對(duì)這個(gè)變量的讀操作。注意:在Java5以前的版本使用了volatile的雙檢鎖還是有問題的。其原因是Java5以前的JMM(Java內(nèi)存模型)是存在缺陷的,即時(shí)將變量聲明成volatile也不能避免重排序。6.枚舉:[java] view plain copypublic class Singleton7 {
public enum EasySingleton{
INSTANCE;
}
}我們可以通過EasySingleton.INSTANCE來訪問實(shí)例,這比調(diào)用getInstance()方法簡(jiǎn)單多了。創(chuàng)建枚舉默認(rèn)就是線程安全,而且還能防止反序列化導(dǎo)致重新創(chuàng)建新的對(duì)象。