最近看一些Java內(nèi)存模型方面的書沼瘫,講了一下Java的對(duì)象的內(nèi)存分配過(guò)程,其中有個(gè)例子講解多線程鎖的問(wèn)題,說(shuō)了下面的例子:
單例寫法 雙重校驗(yàn)寫法
//------------------------雙重校驗(yàn)鎖------------------
private static Singleton singleton2;//-------1
public static Singleton getInstance4() {//------------2
if (singleton2 == null) {//-----------------------3
synchronized (Singleton.class) {//------------4
if (singleton2 == null)//-----------------5
singleton2 = new Singleton();//-------6
}
}
return singleton;
}
問(wèn)題處在了第6步粤策,Java創(chuàng)建對(duì)象的第6步可以分為以下三步:
memory = allocate();//----1
ctorInstance(memory);//-2
instance = memory;//-----3
其中2,3步在JVM編譯優(yōu)化時(shí)可能發(fā)生重排序误窖,這和采用的JIT有關(guān)叮盘,并且該重排序遵循intra-thread semantics法則(重排序后不會(huì)影響單線程的執(zhí)行結(jié)果)。
如果發(fā)生重排序霹俺,第3步先于第2步執(zhí)行柔吼,那么A線程可能只是讓對(duì)象指向內(nèi)存地址,并沒(méi)有實(shí)質(zhì)的初始化對(duì)象丙唧,那么線程B調(diào)用時(shí)就會(huì)發(fā)生錯(cuò)誤愈魏。
解決方案
-
采用volatile
在Java1.5以后,volatile關(guān)鍵字被加強(qiáng)想际,這種重排序不允許在多線程中發(fā)生培漏。
即在對(duì)象聲明加上volatile關(guān)鍵字。
實(shí)質(zhì):禁止編譯的重排序胡本。
-
采用JVM初始化類時(shí)加鎖
JVM的類的初始化階段牌柄,會(huì)獲取鎖,該鎖可以同步多線程對(duì)一個(gè)類的初始化侧甫。
此時(shí)衍生一種稱為:Initialization On Demand Holder idiom的解決方案珊佣。
//-----------------------------靜態(tài)內(nèi)部類---------------
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance3() {
return SingletonHolder.INSTANCE;
}
** 實(shí)質(zhì): **利用JVM的多線程初始化對(duì)象的特性蹋宦,允許重排序,但對(duì)其他線程不可見(jiàn)咒锻。
另外根據(jù)Java語(yǔ)言規(guī)范冷冗,一個(gè)類在一下5種情況會(huì)發(fā)生初始化:
- T是一個(gè)類,并且T的實(shí)例被創(chuàng)建虫碉。
- T是一個(gè)類贾惦,且T中的靜態(tài)方法被調(diào)用
- T是一個(gè)類,且T中的一個(gè)靜態(tài)字段被賦值敦捧。
- T是一個(gè)類须板,且T中的非常量字段被使用
- T是一個(gè)頂級(jí)類(TOP Level Class),有斷言語(yǔ)句嵌套在T內(nèi)部被執(zhí)行兢卵。(assert語(yǔ)句习瑰,很少用改規(guī)則)