單例模式之線程安全的懶漢式
單例模式分為懶漢式和餓漢式,今天主要來講講其中的懶漢式,懶漢式因為要實現(xiàn)懶加載(使用時再創(chuàng)建對象)蓖乘,所以存在線程安全問題。
先來看看最簡單的懶漢式:
public class Singleton {
private static Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
if (instance == null){
instance = new Singleton();
}
return instance;
}
}
可以看到韧骗,當(dāng)我們getInstance時嘉抒,首先要經(jīng)過if判斷,在多線程場景下袍暴,就可能存在多個線程同時進入if判斷些侍,此時對象還未創(chuàng)建,那么就會多個線程都去創(chuàng)建對象容诬,這樣單例模式就被破壞了娩梨。
解決方案:
1. 雙重檢查
先上代碼:
public class Singleton2 {
private static volatile Singleton2 instance;
private Singleton2(){}
public static Singleton2 getInstance(){
if (instance == null){
synchronized (Singleton2.class){
if (instance == null){
instance = new Singleton2();
}
}
}
return instance;
}
}
用兩次判斷加同步代碼塊實現(xiàn)線程安全
用雙重檢查實現(xiàn)的懶漢式沿腰,在多線程場景中g(shù)etIntance時览徒,首先要進行一次對象是否已創(chuàng)建的判斷,如果已創(chuàng)建就直接返回實例颂龙,當(dāng)首次加載需要創(chuàng)建對象時习蓬,假設(shè)有線程A和線程B兩個線程同時通過第一層判斷,那么它們需要排隊進入同步代碼塊措嵌,假設(shè)線程A先進入同步代碼塊躲叼,那么實例由線程A創(chuàng)建,那么當(dāng)線程B進入同代碼塊時便不能通過第二層檢查企巢,即直接返回實例枫慷。這樣便實現(xiàn)了線程安全的懶加載。
關(guān)于volatile關(guān)鍵字:
volatile有兩個作用:保證可見性和防止指令重排
什么是保證可見性呢浪规,就是當(dāng)一個線程在對主內(nèi)存的某一份數(shù)據(jù)進行更改時或听,改完之后會立刻刷新到主內(nèi)存。并且會強制讓緩存了該變量的線程中的數(shù)據(jù)清空笋婿,必須從主內(nèi)存重新讀取最新數(shù)據(jù)誉裆。意思就是所有線程都可以得到最新的數(shù)據(jù),這樣一來就保證了可見性缸濒。
什么是指令重排呢足丢,當(dāng)new一個對象時粱腻,用字節(jié)碼指令分析是三條指令(new、dup斩跌、invokespecial)绍些,這三條指令可能會發(fā)生重排序,引用指向分配地址滔驶,但對象還未創(chuàng)建遇革,導(dǎo)致判空校驗不準(zhǔn)確。
因為創(chuàng)建對象的過程都在同步代碼塊中揭糕,所以此處使用volatile的作用主要是保證可見性萝快。
2.靜態(tài)內(nèi)部類
代碼:
public class Singleton3 {
private Singleton3(){}
private static class SingletonInstance{
private static final Singleton3 INSTANCE = new Singleton3();
}
public static Singleton3 getInstance(){
return SingletonInstance.INSTANCE;
}
}
靜態(tài)內(nèi)部類:1.當(dāng)外部類被裝載時,內(nèi)部類并不會被裝載 當(dāng)使用到時才會被裝載著角,且只裝載一次揪漩。
3.枚舉
代碼:
enum Singleton4 {
INSTANCE;
public void method(){
System.out.println("枚舉實現(xiàn)單例");
}
}
使用:
Singleton4.INSTANCE.method();
枚舉是最簡單也是最好用的實現(xiàn)方式,枚舉的實際是用final修飾的實現(xiàn)enum接口的類吏口,因為枚舉構(gòu)造只能私有奄容,所以枚舉是天生的單例模式
因為枚舉類是在第一次訪問時才被實例化,所以它也是懶加載的产徊。
補充:
除枚舉方式外, 其他方法都會通過反射的方式破壞單例,反射是通過調(diào)用構(gòu)造方法生成新的對象昂勒,所以如果我們想要阻止單例破壞,可以在構(gòu)造方法中進行判斷舟铜,若已有實例, 則阻止生成新的實例戈盈。
如果單例類實現(xiàn)了序列化接口Serializable, 就可以通過反序列化破壞單例,所以我們可以不實現(xiàn)序列化接口,如果非得實現(xiàn)序列化接口谆刨,可以重寫反序列化方法readResolve(), 反序列化時直接返回相關(guān)單例對象塘娶。