實(shí)現(xiàn)單例模式應(yīng)該是面試中比較常見的問題诫硕,下面簡單講講幾種單例模式的實(shí)現(xiàn)方式你踩。
餓漢式
public class Singleton{
public static Singleton instance = new Singleton();
private Singleton();
public static Singleton getInstance(){
return instance;
}
}
優(yōu)點(diǎn):避免線程同步產(chǎn)生的問題,沒有加鎖折柠,效率更高宾娜。
缺點(diǎn):在類加載的時(shí)候就初始化對象,浪費(fèi)內(nèi)存扇售。
懶漢式
V1.0
public class Singleton{
public static Singleton instance = null;
private Singleton();
public static Singleton getInstance(){
if(instance == null){
intance = new Singleton();
}
return instance;
}
}
優(yōu)點(diǎn):需要時(shí)才創(chuàng)建對象前塔,不會(huì)存在內(nèi)存浪費(fèi)。
缺點(diǎn):多線程環(huán)境下不能正常工作承冰,容易創(chuàng)建出多個(gè)對象华弓。
V2.0
public class Singleton{
public static Singleton instance = null;
private Singleton();
public static Singleton getInstance(){
synchronized(this.class){
if(instance == null){
intance = new Singleton();
}
}
return instance;
}
}
優(yōu)點(diǎn):在方法加上synchronized關(guān)鍵字,保證了線程相對安全困乒,不會(huì)創(chuàng)建出多個(gè)對象寂屏。
缺點(diǎn):synchronized會(huì)導(dǎo)致效率低下。
V3.0 雙重校驗(yàn)鎖
public class Singleton{
private static Singleton instance = null;
private Singleton();
public static Singleton getInstance(){
if(instance == null){
synchronized(this.class){
if(instance == null){
singleton = new Singleton();
}
}
}
return instance;
}
}
優(yōu)點(diǎn):既保證線程相對安全娜搂,又不會(huì)導(dǎo)致效率低下的問題迁霎。
缺點(diǎn):
singleton = new Singleton()這句,這并非是一個(gè)原子操作百宇,事實(shí)上在 JVM 中這句話大概做了下面 3 件事情欧引。
- 給 singleton 分配內(nèi)存
- 調(diào)用 Singleton 的構(gòu)造函數(shù)來初始化成員變量,形成實(shí)例
- 將singleton對象指向分配的內(nèi)存空間(執(zhí)行完這步 singleton才是非 null 了)
但是在 JVM 的即時(shí)編譯器中存在指令重排序的優(yōu)化恳谎。也就是說上面的第二步和第三步的順序是不能保證的,最終的執(zhí)行順序可能是 1-2-3也可能是 1-3-2。如果是后者因痛,則在 3 執(zhí)行完畢婚苹、2 未執(zhí)行之前,被線程二搶占了鸵膏,這時(shí) instance 已經(jīng)是非 null 了(但卻沒有初始化)膊升,所以線程二會(huì)直接返回 instance,然后使用谭企,然后順理成章地報(bào)錯(cuò)廓译。
對此,我們只需要把singleton聲明成 volatile 就可以了债查。
V4.0
public class Singleton{
private voletile static Singleton instance = null;
private Singleton();
public static Singleton getInstance(){
if(instance == null){
synchronized(this.class){
if(instance == null){
singleton = new Singleton();
}
}
}
return instance;
}
}
volatile有兩個(gè)作用:
- 這個(gè)變量不會(huì)在多線程中存在多個(gè)副本非区,直接從內(nèi)存中讀取
- 防止指令重排序。也就是說盹廷,在 volatile 變量的賦值操作后面會(huì)有一個(gè)內(nèi)存屏障(生成的匯編代碼上)征绸,讀操作不會(huì)被重排序到內(nèi)存屏障之前。
靜態(tài)內(nèi)部類
public class Singleton{
private Singleton();
private static class SingletonHandler{
private static final Singleton INSTANCE = new Singleton();
}
public static getInstance(){
return SingletonHandler.INSTANCE;
}
}
上面這種方式俄占,仍然使用JVM本身機(jī)制保證了線程安全問題管怠;由于 SingletonHolder 是私有的,除了 getInstance() 之外沒有辦法訪問它缸榄。因此它只有在getInstance()被調(diào)用時(shí)才會(huì)真正創(chuàng)建渤弛;同時(shí)讀取實(shí)例的時(shí)候不會(huì)進(jìn)行同步,沒有性能缺陷甚带;也不依賴 JDK 版本她肯。
枚舉
public enum SingletonV6{
INSTANCE;
}
默認(rèn)枚舉實(shí)例的創(chuàng)建是線程安全的,所以不需要擔(dān)心線程安全的問題欲低,而且因?yàn)镴VM會(huì)阻止反射獲取枚舉類的私有構(gòu)造方法辕宏,無法通過反射的方式構(gòu)建對象。但是在枚舉中的其他任何方法的線程安全由程序員自己負(fù)責(zé)砾莱。
總結(jié)
“陳式第一定理” : “無論你的代碼寫得有多好瑞筐,其只能在特定的范圍內(nèi)工作,超出這個(gè)范圍就要出Bug了”
如果我們的這個(gè)Singleton類是一個(gè)關(guān)于我們程序配置信息的類腊瑟。我們需要它有序列化的功能聚假,那么,當(dāng)反序列化的時(shí)候闰非,我們將無法控制別人不多次反序列化膘格。
不過,我們可以利用一下Serializable接口的readResolve()方法财松,比如
class SingletonV7 implements Serializable {
// ......
// ......
protected Object readResolve() {
return getInstance();
}
public static Object getInstance(){
return instance;
}
}
參考: