【java 多線程】安全的構(gòu)造器

測(cè)試和調(diào)試多線程程序是非常困難的,因?yàn)椴l(fā)危險(xiǎn)常常不會(huì)穩(wěn)定的發(fā)生。多數(shù)線程問(wèn)題都是不可預(yù)測(cè)的,可能不會(huì)發(fā)生在所有特定的平臺(tái)(如單處理器系統(tǒng))或低于某個(gè)特定水平荷載。因?yàn)闇y(cè)試多線程的正確性是很困難的秕硝,而且bug可能會(huì)花很長(zhǎng)時(shí)間才能重現(xiàn),所以洲尊,當(dāng)我們開(kāi)發(fā)程序是远豺,要從一開(kāi)始就考慮線程安全問(wèn)題。

危險(xiǎn)的條件

大多數(shù)并發(fā)危害歸結(jié)為某種數(shù)據(jù)競(jìng)爭(zhēng)坞嘀。數(shù)據(jù)競(jìng)爭(zhēng)或競(jìng)爭(zhēng)條件躯护,當(dāng)多個(gè)線程或進(jìn)程讀寫(xiě)共享數(shù)據(jù)項(xiàng),最終結(jié)果取決于線程的調(diào)度的順序。清單1給出一個(gè)示例的一個(gè)簡(jiǎn)單的數(shù)據(jù)競(jìng)爭(zhēng)實(shí)例丽涩,根據(jù)調(diào)度的線程可以打印0或1棺滞。

public class DataRace {
  static int a = 0;
 
  public static void main() {
    new MyThread().start();
    a = 1;
  }
 
  public static class MyThread extends Thread {
    public void run() { 
      System.out.println(a);
    }
  }
}

第二個(gè)線程可能被馬上調(diào)度裁蚁,打印初始a的初始值0。此外继准,第二個(gè)線程可能不會(huì)立即執(zhí)行枉证,導(dǎo)致打印1。這個(gè)程序的輸出結(jié)果依賴你使用的JDK移必,操作系統(tǒng)的調(diào)度室谚。多運(yùn)行幾次會(huì)得到不同的結(jié)果。

可見(jiàn)性危害

在清代1 事實(shí)上還有另一個(gè)數(shù)據(jù)沖突崔泵,除了在第二個(gè)線程執(zhí)行之前秒赤,第一個(gè)線程把a(bǔ)設(shè)置為1。第二個(gè)沖突時(shí)可見(jiàn)性沖突:兩個(gè)線程沒(méi)有使用 synchronization憎瘸,如果第二個(gè)線程在第一個(gè)線程賦值a之后執(zhí)行入篮,第一個(gè)線程的賦值可能或不可能立刻對(duì)第二個(gè)線程可見(jiàn)。第二個(gè)線程可能看到的a還是0幌甘,計(jì)時(shí)線程1已經(jīng)設(shè)置成了1,潮售。

這二類數(shù)據(jù)沖突,兩個(gè)線程在沒(méi)有適當(dāng)?shù)耐皆L問(wèn)相同的變量锅风,是一個(gè)復(fù)雜的問(wèn)題饲做,但幸運(yùn)的是,你可以避免這類沖突利用同步遏弱,當(dāng)你閱讀一個(gè)變量,可能是由另一個(gè)線程寫(xiě)入的數(shù)據(jù)塞弊,或者寫(xiě)一個(gè)變量就被由另一個(gè)線程讀取漱逸。我們不會(huì)對(duì)這類數(shù)據(jù)競(jìng)爭(zhēng)在這里進(jìn)一步探索,請(qǐng)看 "Synching up with the Java Memory Model" 側(cè)邊欄和相關(guān)課題組對(duì)這一復(fù)雜問(wèn)題的更多信息游沿。

在構(gòu)造方法中不要公布“this”指針

將數(shù)據(jù)競(jìng)爭(zhēng)引入到類中的一個(gè)錯(cuò)誤是在構(gòu)造函數(shù)完成之前將this引用暴露給另一個(gè)線程饰抒。有時(shí)引用是顯式的,例如直接將其存儲(chǔ)在靜態(tài)字段或集合中诀黍,但其他時(shí)間可以是隱式的袋坑,例如在構(gòu)造函數(shù)中向非靜態(tài)內(nèi)部類的實(shí)例發(fā)布引用時(shí)。構(gòu)造函數(shù)不是普通的方法眯勾,它們具有初始化安全的特殊語(yǔ)義枣宫。在構(gòu)造函數(shù)完成后,對(duì)象被假定為可預(yù)測(cè)的吃环、一致的狀態(tài)也颤,并且對(duì)未完全構(gòu)造對(duì)象的引用是危險(xiǎn)的。清單2展示了將這種競(jìng)爭(zhēng)條件引入構(gòu)造函數(shù)的示例郁轻。它看起來(lái)無(wú)害翅娶,但它包含了嚴(yán)重并發(fā)問(wèn)題的種子文留。

public class EventListener { 
 
  public EventListener(EventSource eventSource) {
    // do our initialization
    ...
 
    // register ourselves with the event source
    eventSource.registerListener(this);
  }
 
  public onEvent(Event e) { 
    // handle the event
  }
}

初次檢查,事件偵聽(tīng)器類看起來(lái)是無(wú)害的竭沫。偵聽(tīng)器的注冊(cè)是一個(gè)新的對(duì)象燥翅,而另一個(gè)線程可能看到它,這是構(gòu)造函數(shù)所做的最后一件事蜕提。但即使忽略所有的java內(nèi)存模型(JMM)如在線程和內(nèi)存訪問(wèn)排序能見(jiàn)度差異問(wèn)題森书,這個(gè)代碼仍然是不完全暴露在了危險(xiǎn)事件偵聽(tīng)器對(duì)象的其他線程」峤Γ考慮會(huì)發(fā)生什么時(shí)拄氯,EventListener是它的子類,如清單3所示:

public class RecordingEventListener extends EventListener {
  private final ArrayList list;
 
  public RecordingEventListener(EventSource eventSource) {
    super(eventSource);
    list = Collections.synchronizedList(new ArrayList());
  }
 
  public onEvent(Event e) { 
    list.add(e);
    super.onEvent(e);
  }
 
  public Event[] getEvents() {
    return (Event[]) list.toArray(new Event[0]);
  }
}

因?yàn)閖ava語(yǔ)言規(guī)范要求調(diào)用super()必須是子類構(gòu)造方法的第一行代碼它浅。在子類完成初始化之前译柏,我們尚未構(gòu)造完成的event listener 已經(jīng)注冊(cè)到了event source。現(xiàn)在我們的list存在數(shù)據(jù)競(jìng)爭(zhēng)姐霍。RecordingEventListener.onEvent()在調(diào)用時(shí)可能list還是默認(rèn)值null鄙麦,會(huì)拋出NullPointerException 異常。

參考

Safe construction techniques
JSR 133 (Java Memory Model) FAQ

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末镊折,一起剝皮案震驚了整個(gè)濱河市胯府,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌恨胚,老刑警劉巖骂因,帶你破解...
    沈念sama閱讀 216,997評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異赃泡,居然都是意外死亡寒波,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,603評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)升熊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)俄烁,“玉大人,你說(shuō)我怎么就攤上這事级野∫惩溃” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,359評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵蓖柔,是天一觀的道長(zhǎng)辰企。 經(jīng)常有香客問(wèn)我,道長(zhǎng)况鸣,這世上最難降的妖魔是什么蟆豫? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,309評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮懒闷,結(jié)果婚禮上十减,老公的妹妹穿的比我還像新娘栈幸。我一直安慰自己,他們只是感情好帮辟,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,346評(píng)論 6 390
  • 文/花漫 我一把揭開(kāi)白布速址。 她就那樣靜靜地躺著,像睡著了一般由驹。 火紅的嫁衣襯著肌膚如雪芍锚。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,258評(píng)論 1 300
  • 那天蔓榄,我揣著相機(jī)與錄音并炮,去河邊找鬼。 笑死甥郑,一個(gè)胖子當(dāng)著我的面吹牛逃魄,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播澜搅,決...
    沈念sama閱讀 40,122評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼伍俘,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了勉躺?” 一聲冷哼從身側(cè)響起癌瘾,我...
    開(kāi)封第一講書(shū)人閱讀 38,970評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎饵溅,沒(méi)想到半個(gè)月后妨退,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,403評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蜕企,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,596評(píng)論 3 334
  • 正文 我和宋清朗相戀三年咬荷,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片糖赔。...
    茶點(diǎn)故事閱讀 39,769評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖轩端,靈堂內(nèi)的尸體忽然破棺而出放典,到底是詐尸還是另有隱情,我是刑警寧澤基茵,帶...
    沈念sama閱讀 35,464評(píng)論 5 344
  • 正文 年R本政府宣布奋构,位于F島的核電站,受9級(jí)特大地震影響拱层,放射性物質(zhì)發(fā)生泄漏弥臼。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,075評(píng)論 3 327
  • 文/蒙蒙 一根灯、第九天 我趴在偏房一處隱蔽的房頂上張望径缅。 院中可真熱鬧掺栅,春花似錦、人聲如沸纳猪。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,705評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)氏堤。三九已至沙绝,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間鼠锈,已是汗流浹背闪檬。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,848評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留购笆,地道東北人粗悯。 一個(gè)月前我還...
    沈念sama閱讀 47,831評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像由桌,于是被迫代替她去往敵國(guó)和親为黎。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,678評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容