java如何安全的延遲初始化?
我們經(jīng)常需要使用單例模式來為我們工作,而我們一般會(huì)使用下面的代碼來組織我們的單例模式:
我們稱這樣的代碼為“雙重檢查鎖定”(Double checking locking),一般情況下,這樣的代碼不會(huì)出現(xiàn)什么問題,畢竟我們的代碼中也有類似的代碼,但是這樣的代碼還是會(huì)有安全隱患。在這里,我們需要了解一個(gè)“new SingletonClass()”背后的過程,新生成一個(gè)對(duì)對(duì)象需要三個(gè)過程:
1、申請(qǐng)足夠大小的內(nèi)存空間
2、初始化申請(qǐng)到的內(nèi)存空間
3、將新對(duì)象指向申請(qǐng)的內(nèi)存空間
如果按照1-2-3的順序來的話是沒有問題的,但是編譯器有可能會(huì)為了達(dá)到最好的效率對(duì)指令進(jìn)行重排序,對(duì)于不會(huì)影響執(zhí)行結(jié)果的指令,編譯器可以進(jìn)行指令沖排序,上面的2和3之間沒有依賴關(guān)系,所以可以進(jìn)行重排序,所以最后new的動(dòng)作的執(zhí)行順序可能為1-3-2,而只要3執(zhí)行了,那么我們的對(duì)象就不是null了啊,雖然還沒有初始化。而問題就在這里,假設(shè)線程A執(zhí)行到了“new SingletonClass()”這一句,然后new動(dòng)作的指令被重新排序?yàn)?-3-2,我們假設(shè)線程A執(zhí)行到3的時(shí)候(還沒有執(zhí)行2),線程B來檢測(cè)“_instance == null”,而線程A已經(jīng)使得“_instance != null”成立了,所以線程B不再繼續(xù)執(zhí)行,但是我們發(fā)現(xiàn)線程B獲取到的是一個(gè)還沒有初始化的對(duì)象實(shí)例,這樣做是有安全隱患的,假若線程B獲取到_instance之后立刻操作這個(gè)對(duì)象就會(huì)出現(xiàn)問題。
經(jīng)過我們的分析,我們發(fā)現(xiàn)問題在于new的指令被編譯器重排序了,我們有兩種方法來解決上面的問題:
一、使用volatile來禁止指令重新排序
只需在_instance之前加上volatile,我們就可以解除安全隱患。
二、使用類初始化特性來解決,JVM在進(jìn)行類初始化時(shí),JVM會(huì)去獲取一個(gè)鎖,可以同步多個(gè)線程對(duì)同一個(gè)類的初始化。這也是我們?cè)诖a里面見到最多的方式(正確的)。
假設(shè)有兩個(gè)線程同時(shí)需要初始化類SingletonClass,那么都需要首先獲取一個(gè)鎖,獲取鎖成功的線程可以進(jìn)行初始化工作,沒有獲取到的線程只能等待,而同一個(gè)線程內(nèi)new的指令重排序是不影響最后結(jié)果的。