最近常在代碼中見到延遲初始化瓜饥,想和大家聊聊這個(gè)小話題郁季。最簡(jiǎn)單的延遲初始化很容易想傲绣,提起鍵盤我就能敲出來啊:
public class DelayInitializationWithSingleThread {
private static Object foo;
public static Object getInstance() {
if(foo == null)
this.foo = new Object();
return this.foo;
}
}
這個(gè)延遲初始化掠哥,單線程下很完美,多線程下豈不是要亂套秃诵!那簡(jiǎn)單续搀,那我在方法上加一個(gè)同步操作好了:
public class DelayInitializationWithMultiThread {
private static Object foo;
public synchronized static Object getInstance() {
if(foo == null)
this.foo = new Object();
return this.foo;
}
}
但是加鎖需要上下文切換,是個(gè)很耗系統(tǒng)資源的操作菠净,每次調(diào)用getInstance()都要執(zhí)行一次上下文切換禁舷,有些沒必要。我只需要在第一次初始化的時(shí)候進(jìn)行加鎖就好了啊毅往,這就是著名的DCL(Double Check Lock):
public class DelayInitializationWithDCL {
private static Object foo;
public synchronized static Object getInstance() {
if(foo == null) {
synchronized (Test.class) {
if(foo == null)
this.foo = new Object();
}
}
return this.foo;
}
}
看起來很不錯(cuò)牵咙。
等一下,還記得JVM的可見性嗎攀唯?每個(gè)線程的變量都是在各自線程的緩存中洁桌,各自的工作變量相互不可見,必須等到JVM將其刷到公用的主存里才能對(duì)其他線程可見侯嘀。也就是說A線程初始化foo后另凌,B線程不一定能看得到,在不知道的情況下戒幔,那B會(huì)再執(zhí)行一遍初始化吠谢,C線程也不一定看得見,他繼續(xù)诗茎。工坊。。這還只是個(gè)簡(jiǎn)單的示例代碼敢订,要是工作代碼執(zhí)行一些復(fù)雜的初始化的話王污,那不是可能造成對(duì)象失效或者臟數(shù)據(jù)?好吧我不敢再往下想了楚午,趕緊給變量foo加上消除線程緩存作用的volatile
關(guān)鍵字昭齐。
public class DelayInitializationWithVolatiledDCL {
private volatile static Object foo;
public synchronized static Object getInstance() {
if(foo == null) {
synchronized (Test.class) {
if(foo == null)
this.foo = new Object();
}
}
return this.foo;
}
}
呼呼。醒叁。這下終于完成了司浪,看著很完美泊业。但是啊把沼,有些復(fù)雜,萬一哪天我頭腦發(fā)熱吁伺,忘記加volatile
咋辦呢饮睬。好吧,為了有更好的方案篮奄,繼續(xù)改造:
public class DelayInitializationWithInitalizationPlaceHolder {
private static class ResourceHolder {
public static Object foo = new Object();
}
public synchronized Object getInstance() {
return ResourceHolder.foo;
}
}
當(dāng)getInstance()第一次被調(diào)用時(shí)捆愁,JVM會(huì)加載ResourceHolder類割去,并將其靜態(tài)變量初始化,以后再次調(diào)用就能直接獲取到初始化的foo了昼丑,不需要加鎖呻逆,不需要volatile,簡(jiǎn)直太棒了菩帝,完美咖城!