爭用條件
據說 爭用條件 存在於這樣的系統中多個線程之間存在對共享資源的競爭而勝出者決定系統的行為Allen Holub 在他撰寫的文章 programming Java threads in the real world 提供了一個帶有這樣 bug 的簡單的多線程程序示例在沖突的訪問請求之間進行不正確同步的另一個更可怕的後果是 數據崩潰此時共享的數據結構有一部分由一個線程更新而另一部分由另一個線程更新在這種情況下系統的行為不是按照勝出線程的意圖進行系統根本不按照任何一個線程的意圖行動所以兩個線程最後都將以失敗告終
死鎖
死鎖 的情況是指線程由於等候某種條件變成真(例如資源可以使用)但是它等候的條件無法變成真因為能夠讓條件變成真的線程在等候第一個線程做某件事這樣兩個線程都在等候對方先采取第一步所以都無法做事
活動鎖
活動鎖 與 死鎖 不同它是在線程實際工作的時候發生的但這時還沒有完成工作這通常是在兩個線程交叉工作的時候發生所以第一個線程做的工作被另一個線程取消一個簡單的示例就是每個線程已經擁有了一個對象同時需要另外一個線程擁有的另外一個對象可以想像這樣的情況每個線程放下自己擁有的對象撿起另外一個線程放下的對象顯然這兩個線程會永遠都運行在上鎖這一步操作上結果是什麼都做不成(常見的真實示例就是兩個人在狹窄的走廊相遇每個人都禮貌地讓到另一邊讓對方先行但卻在相同的時間都讓到同一邊了所以兩個人還都沒法通過這種情況會持續一些時間然後兩個人都從這邊閃到那邊結果還是一點進展也沒有)
資源耗盡
資源耗盡又稱為 線程耗盡是 Java 語言的 wait/notify 原語無法保證 liveness 的後果Java 強制這些方法要擁有它們等候或通知的對象的鎖在某個線程上調用的 wait() 方法在開始等候之前必須釋放監視器鎖然後在從方法返回並獲得通知之後必須再次重新獲得鎖因此Java 語言規范在鎖本身之外還描述了一套與每個對象相關的 等候集(wait set)一旦線程釋放了對象上的鎖(在 wait 的調用之後)線程就會放在這個等候集上
多數 JVM 實現把等候線程放在隊列中所以如果在通知發生的時候還有其他線程在等候監視器那麼就會把一個新線程放在隊列尾部而它並不是下一個獲得鎖的線程所以等到被通知線程實際得到監視器的時候通知該線程的條件可能已經不再為真所以它不得不再次 wait這種情況可能無限持續下去從而造成運算工作上浪費(因為要反復把該線程放入等候集和從中取出)和線程耗盡
貪心哲學家的寓言
演示這種行為的原型示例是 Peter Welch 教授描述的聰明人沒有雞肉在這個場景中考慮的系統是一所由五位哲學家一位廚師和一個食堂組成的學院所有的哲學家(除了一位)都要想想(在代碼示例中考慮的時間是 秒)之後才去食堂取飯而貪心的哲學家則不想把時間浪費在思考上 —— 相反他一次又一次地回到食堂企圖拿到雞肉來吃
廚師按照一批四份的定量准備雞肉每准備好一批就送到食堂貪心的哲學家不斷地去廚房但他總是錯過食物!事情是這樣的他第一次到的時候時間太早廚師還沒開火因此貪心的哲學家只好干等著(通過 wait() 方法調用)在開飯的時候(通過 notify() 方法調用)貪心的哲學家再一次回到食堂排隊等候但是這次在他前來等候的時候他的四位同事已經到了所以他在食堂隊列中的位置在他們後面他的同事們把廚房送來的一批四份雞肉全部拿走了所以貪心的哲學家又要在一邊等著了 可憐(也可能是公平的) 他永遠處在這個循環之外
驗證的問題
一般來說很難按照普通的規范對 Java 編程的多線程程序進行驗證同樣開發自動化工具對於常見的並發問題(例如死鎖活動鎖和資源耗盡)進行完整而簡單的分析也不太容易——特別是在任意 Java 程序中或者在缺乏並發的正式模型的時候
更糟的是並發性問題出了名的變化多端難於跟蹤每個 Java 開發人員都曾經聽說過(或者親自編寫過)這樣的 Java 程序經過嚴格分析而且正常運行了相當一段時間沒有表現出潛在的死鎖然後突然有一天問題發生了結果弄得開發團隊經歷許多的不眠之夜來試圖發現並修補根本原因
一方面多線程 Java 程序容易發生的錯誤非常不明顯有可能在任意什麼時候發生另一方面完全有可能這些 bug 在程序中從不出現問題取決於一些不可知的因素多線程程序的復雜本質使得人們很難有效地對其進行驗證沒有一套現成的規則可以找出多線程代碼中的這類問題也無法確切地證明這些問題不存在這些導致許多 Java 開發人員完全避開多線程應用程序的設計和開發即使用並發和並行的方式對系統進行建模會非常棒他們也不使用多線程
確實想進行多線程編程的開發人員通常准備好了以下一個或兩個解決方案(至少是一部分)
長時間艱苦地測試代碼找出所有出現的並發性問題誠心地希望到應用程序真正運行地時候已經發現並修復了所有這類問題
大量運行設計模式和為多線程編程建立的指導原則但是這類指導原則只在整個系統都按照它們的規范設計的時候才有效沒有設計規則能夠覆蓋所有類型的系統
雖然知道的人不多但是對於編寫(然後驗證)正確的多線程應用程序這一問題還有第三個選項使用稱為通信順序進程( Communicating Sequential ProcessesCSP)的精確的線程同步的數學理論可以在設計時最好地處理死鎖和活動鎖之類的問題CSP 由 CAR Hoare 與 世紀 年代後期設計CSP 提供了有效的方法證明用它的構造和工具構建的系統可以免除並發的常見問題
結束語
在這份面向 Java 程序員的 CSP 全面介紹中我把重點放在克服多線程應用程序開發常見問題的第一步上即了解這些問題我介紹了 Java 平台上目前支持的多線程編程構造解釋了它們的起源討論了這類程序可能會有的問題我還解釋了用正式理論在任意的大型的和復雜的應用程序中清除這些問題(即競爭冒險死鎖活動鎖和資源耗盡)或者證明這些問題不存在的困難
[] []
From:http://tw.wingwit.com/Article/program/Java/gj/201311/27770.html