單例模式
單例模式有以下特點(diǎn):
- 單例類(lèi)只能有一個(gè)實(shí)例
- 單例類(lèi)必須自己創(chuàng)建自己的唯一實(shí)例
- 單例類(lèi)必須給所有其他對(duì)象提供這一實(shí)例
優(yōu)點(diǎn):
- 只有一個(gè)實(shí)例,可以減少內(nèi)存開(kāi)銷(xiāo)
- 避免對(duì)資源的多重占用
- 設(shè)置全局資源訪問(wèn)
單例的幾種寫(xiě)法:
/************手寫(xiě)一定要自己動(dòng)手手寫(xiě) 面試你會(huì)發(fā)現(xiàn)全是手寫(xiě)一個(gè)單例模式開(kāi)始***********/
- 懶漢式
public class Singleton{
private static Singleton single=null;
private Singleton(){}
public static Singleton getInstance(){
if (single==null){
single=new Singleton();
}
return single;
}
}
/**
* <pre>
* author : lzy
* e-mail : zanyang.lin@newbeeair.com
* time : 2017/07/11
* desc : 單例模式的幾種寫(xiě)法
* </pre>
*/
public class Singleton {
private static Singleton single=null;
private Singleton (){}
public static Singleton getIntstance(){
if (single==null){
single=new Singleton();
}
return single;
}
}
這種寫(xiě)法沒(méi)有考慮線程安全問(wèn)題宦芦,在并發(fā)環(huán)境下可能出現(xiàn)多個(gè)singleton實(shí)例
保證線程安全修改如下
1拍柒、解決了線程同步 不過(guò)效率較低
public class Singleton {
private static Singleton single=null;
private Singleton (){}
//添加同步鎖
public static synchronized Singleton getIntstance(){
if (single==null){
single=new Singleton();
}
return single;
}
}
2喇嘱、在并發(fā)量不高 安全性不高情況下可以很好運(yùn)行 同時(shí)避免了每次都同步的性能損耗
package com.example.singleton;
/**
* <pre>
* author : lzy
* e-mail : zanyang.lin@newbeeair.com
* time : 2017/07/11
* desc : 單例模式的幾種寫(xiě)法
* </pre>
*/
public class Singleton {
private volatile static Singleton single = null;
private Singleton() {
}
//添加同步鎖
public static Singleton getIntstance() {
if (single == null) {
//雙重檢查鎖
synchronized (Singleton.class) {
if (single == null) {
single = new Singleton();
}
}
}
return single;
}
}
volatile
- 原子操作
簡(jiǎn)單來(lái)說(shuō)魏铅,原子操作(atomic)就是不可分割的操作,在計(jì)算機(jī)中盏档,就是指不會(huì)因?yàn)榫€程調(diào)度被打斷的操作澈吨。比如把敢,簡(jiǎn)單的賦值是一個(gè)原子操作:m = 6; // 這是個(gè)原子操作
假如m原先的值為0,那么對(duì)于這個(gè)操作谅辣,要么執(zhí)行成功m變成了6修赞,要么是沒(méi)執(zhí)行m還是0,而不會(huì)出現(xiàn)諸如m=3這種中間態(tài)——即使是在并發(fā)的線程中桑阶。而柏副,聲明并賦值就不是一個(gè)原子操作:int n = 6; // 這不是一個(gè)原子操作對(duì)于這個(gè)語(yǔ)句,至少有兩個(gè)操作:①聲明一個(gè)變量n②給n賦值為6——這樣就會(huì)有一個(gè)中間狀態(tài):變量n已經(jīng)被聲明了但是還沒(méi)有被賦值的狀態(tài)联逻〈瓿叮——這樣检痰,在多線程中包归,由于線程執(zhí)行順序的不確定性,如果兩個(gè)線程都使用m铅歼,就可能會(huì)導(dǎo)致不穩(wěn)定的結(jié)果出現(xiàn)公壤。 - 指令重排
簡(jiǎn)單來(lái)說(shuō),就是計(jì)算機(jī)為了提高執(zhí)行效率椎椰,會(huì)做的一些優(yōu)化厦幅,在不影響最終結(jié)果的情況下,可能會(huì)對(duì)一些語(yǔ)句的執(zhí)行順序進(jìn)行調(diào)整慨飘。比如确憨,這一段代碼:
int a ; // 語(yǔ)句1
a = 8 ; // 語(yǔ)句2
int b = 9 ; // 語(yǔ)句3
int c = a + b ; // 語(yǔ)句4
正常來(lái)說(shuō),對(duì)于順序結(jié)構(gòu)瓤的,執(zhí)行的順序是自上到下休弃,也即1234。但是圈膏,由于指令重排
的原因塔猾,因?yàn)椴挥绊懽罱K的結(jié)果,所以稽坤,實(shí)際執(zhí)行的順序可能會(huì)變成3124或者1324丈甸。由于語(yǔ)句3和4沒(méi)有原子性的問(wèn)題糯俗,語(yǔ)句3和語(yǔ)句4也可能會(huì)拆分成原子操作,再重排睦擂〉孟妫——也就是說(shuō),對(duì)于非原子性的操作祈匙,在不影響最終結(jié)果的情況下忽刽,其拆分成的原子操作可能會(huì)被重新排列執(zhí)行順序。
OK夺欲,了解了原子操作和指令重排的概念之后跪帝,我們?cè)倮^續(xù)看雙重校驗(yàn)鎖方式單例模式代碼的問(wèn)題。下面這段話直接從陳皓的文章(深入淺出單實(shí)例SINGLETON設(shè)計(jì)模式)中復(fù)制而來(lái):主要在于singleton = new Singleton()這句些阅,這并非是一個(gè)原子操作伞剑,事實(shí)上在 JVM 中這句話大概做了下面 3 件事情:
1). 給 singleton 分配內(nèi)存
2). 調(diào)用 Singleton 的構(gòu)造函數(shù)來(lái)初始化成員變量,形成實(shí)例
3). 將singleton對(duì)象指向分配的內(nèi)存空間(執(zhí)行完這步 singleton才是非 null 了)但是在 JVM 的即時(shí)編譯器中存在指令重排序的優(yōu)化市埋。
也就是說(shuō)上面的第二步和第三步的順序是不能保證的黎泣,最終的執(zhí)行順序可能是 1-2-3 也可能是 1-3-2。如果是后者缤谎,則在 3 執(zhí)行完畢抒倚、2 未執(zhí)行之前,被線程二搶占了坷澡,這時(shí) instance 已經(jīng)是非 null 了(但卻沒(méi)有初始化)托呕,所以線程二會(huì)直接返回 instance,然后使用频敛,然后順理成章地報(bào)錯(cuò)项郊。
再稍微解釋一下,就是說(shuō)斟赚,由于有一個(gè)『instance已經(jīng)不為null但是仍沒(méi)有完成初始化』的中間狀態(tài)着降,而這個(gè)時(shí)候,如果有其他線程剛好運(yùn)行到第一層if (instance == null)這里拗军,這里讀取到的instance已經(jīng)不為null了任洞,所以就直接把這個(gè)中間狀態(tài)的instance拿去用了,就會(huì)產(chǎn)生問(wèn)題发侵。這里的關(guān)鍵在于——線程T1對(duì)instance的寫(xiě)操作沒(méi)有完成交掏,線程T2就執(zhí)行了讀操作。
volatilel作用 禁止指令重排
volatile關(guān)鍵字的一個(gè)作用是禁止指令重排器紧,把instance聲明為volatile之后耀销,對(duì)它的寫(xiě)操作就會(huì)有一個(gè)內(nèi)存屏障(什么是內(nèi)存屏障?),這樣熊尉,在它的賦值完成之前罐柳,就不用會(huì)調(diào)用讀操作。
注意:volatile阻止的不singleton = new Singleton()這句話內(nèi)部[1-2-3]的指令重排狰住,而是保證了在一個(gè)寫(xiě)操作([1-2-3])完成之前张吉,不會(huì)調(diào)用讀操作(if (instance == null))。
3催植、靜態(tài)內(nèi)部類(lèi) 延遲加載肮蛹,線程安全 減少內(nèi)存消耗
public class Singleton {
private static class Holder{
private static final Singleton instance=new Singleton();
}
private Singleton(){}
public static final Singleton getInstance(){
return Holder.instance;
}
}
- 餓漢式
public class Singleton{
private static final Singleton single=new Singleton();
private Singleton(){}
public static Singleton getIntstance(){
return single;
}
}
兩者區(qū)別:
- 餓漢式是類(lèi)一旦加載就把單例初始化完成 保證getinstance的時(shí)候 是已經(jīng)存在的
- 懶漢只有當(dāng)調(diào)用getInstance的時(shí)候才去初始化這個(gè)單例
- 餓漢天生是線程安全的
- 懶漢本身非線程安全 為了實(shí)現(xiàn)線程安全有幾種寫(xiě)法