定義
確保某個類只有一個實例,而且自行實例化并向整個系統(tǒng)提供這個實例薪捍。
創(chuàng)建方式
/**
* 餓漢式
*
* 類加載時笼痹,實例就跟創(chuàng)建初始化了,所以是線程安全 (類加載的過程就是線程安全的)
* 不支持懶加載
*/
public class SingleHunger {
private static SingleHunger instance = new SingleHunger();
private SingleHunger() {
}
public static SingleHunger getInstance() {
return instance;
}
}
/**
* 懶漢式
*
* 線程安全
* 因為在 getInstance方法加了個鎖飘诗,那么每次在使用這個單例時与倡,我們都會調(diào)用getInstance方法,
* 那么也就每次都需要加鎖昆稿、釋放鎖了纺座。 如果單例使用比較頻繁的話,這種方式就不是很好了
*/
public class SingleLazy {
private SingleLazy instance;
private SingleLazy(){
}
private synchronized SingleLazy getInstance(){
if (instance == null)
instance = new SingleLazy();
return instance;
}
}
/**
* 雙重校驗
*/
public class SingleDoubleCheck {
private volatile SingleDoubleCheck instance;
private SingleDoubleCheck(){
}
private SingleDoubleCheck getInstance(){
if (instance == null){
synchronized (SingleDoubleCheck.class){
if (instance == null){
instance = new SingleDoubleCheck();
}
}
}
return instance;
}
}
雙重鎖校驗為什么instance要使用關(guān)鍵字`volatile`
因為 `instance = new Single()` 這句賦值代碼不是原子操作溉潭,這句代碼包含以下3個步驟
1. 給 `new Single()`所即將創(chuàng)建的對象分配內(nèi)存空間
2. 初始化對象 净响,即執(zhí)行`new Single()`構(gòu)造方法
3. 把創(chuàng)建對象的內(nèi)存空間地址賦值給`instance`
編譯器指令重排可能會把2、3步的順序顛倒喳瓣,這樣的話馋贤,`Single`對象還沒真正初始化,但內(nèi)存地址已經(jīng)被賦值給了變量`instance`畏陕,
這樣其他線程判斷`instance`不為空配乓,就直接拿去使用了,但instance其實并沒有真正的被初始化惠毁,也就出現(xiàn)了問題犹芹。
所以使用關(guān)鍵字`volatile`來修飾`instance`禁止指令重排。
`volatile` 解決有序性:
采用部分禁止指令重排鞠绰。
對于volatile修飾的變量執(zhí)行寫操作腰埂,禁止位于其前面的讀寫操作與其進行重排序;
對于volatile修飾的變量執(zhí)行讀操作蜈膨,禁止位于其后面的讀寫操作與其進行重排序
這樣屿笼,步驟3是對`instance`進行寫操作牺荠,根據(jù)`volatile`指令禁止重排規(guī)則,
位于其前面的讀寫操作禁止與其進行重排序驴一,那么步驟2也就不能和步驟3指令重排序了休雌。
/**
* 靜態(tài)內(nèi)部類
*/
public class SingleStatic {
private SingleStatic() {
}
public static class SingleHandler {
private static final SingleStatic instance = new SingleStatic();
}
public static SingleStatic getInstance() {
return SingleHandler.instance;
}
}
為什么這種方式創(chuàng)建的單例是支持懶加載的?
答:靜態(tài)變量的初始化是在類加載時蛔趴,類是在用到它的時候才被加載的挑辆。
所以instance真正初始化 時機是在調(diào)用getInstance方法時。
而且靜態(tài)變量只會初始化一次孝情,所以我們后續(xù)調(diào)用getInstance得到的是同一個實例。
為什么是線程安全的洒嗤?
答:因為getInstance整個流程都是在類加載過程中完成的箫荡,而類的加載機制是線程安全的。
不用靜態(tài)內(nèi)部類可以嗎渔隶?
答:如果是普通內(nèi)部類的話羔挡,是不可以聲明靜態(tài)變量的。
如果instance不是靜態(tài)的间唉,那么就無法在類加載過程中初始化绞灼,也就無法保證線程安全了。