多執行緒的風險漫談

coderidea發表於2018-09-17

執行緒的風險

Java對執行緒內建支援是一把雙刃劍。它通過提供語言和類庫,以及一個規範的跨平臺儲存模型,簡化了併發應用的開發。這樣做同時提高了開發人員門檻,因為更多的程式需要使用執行緒,主流的開發人員都必須知道執行緒安全性的問題。

併發危險:競爭條件(race condition)。因為執行緒共享相同的記憶體地址空間,且併發的執行,它們可能訪問或修改其他執行緒正在使用的變數。這其中存在著巨大風險,當資料以外改變時,執行緒會出現混亂。給順序程式設計模型引入了一些非順序因素,這可能造成混亂,並且難以發現錯誤的原因。為了使多執行緒程式的行為可預見,訪問共享變數必須經過合理的協調,這樣執行緒才不會相互干擾。Java提供了同步機制來協調這樣的訪問。

活躍度的危險:活躍度失敗(liveness failure)。當一個活動進入某種它永遠無法繼續執行的狀態時,活躍度失敗就發生了。包括死鎖(dead lock)、飢餓(starvation)、活鎖(livelock)。

效能危險: 效能問題包括服務時間、響應性、吞吐量、資源消費或者可伸縮性

的不良表現。設計良好的應用使用執行緒,能夠獲得純粹的效能收益,但是執行緒會給執行時帶來一定程式的開銷。上下文切換(Context switches)——當排程程式臨時掛起當前執行的執行緒時,另一個執行緒開始執行——這在多個執行緒組成的應用程式中是很頻繁的,並且帶來巨大的系統開銷;儲存和恢復執行緒執行的上下文,離開執行的現場,並且CPU的時間會花費在對執行緒的排程上。當執行緒共享資料的時候,它們必須使用同步機制,這個機制會限制編譯器的優化,能夠清空或鎖定記憶體和快取記憶體,並在共享記憶體匯流排上建立同步通訊。這些因素引入了新的效能開銷。

 

執行緒安全

編寫執行緒安全的程式碼,本質上就是管理對狀態(state)的訪問,而且通常都是共享(一個變數可以被多個執行緒訪問)、可變(變數的值在其生命週期內可以改)的狀態。

什麼是執行緒安全

當多個執行緒訪問一個類時,如果不用考慮這些執行緒在執行時環境下的排程和交替執行,並且不需要額外的的同步及在呼叫方程式碼不必作其他的協調,這個類的行為仍然是正確的,那麼稱這個類是執行緒安全的。

無狀態的物件永遠是執行緒安全的。

原子性:假設有操作A和操作B,如果從執行A的執行緒角度看,當其他執行緒執行B時,要麼B全部執行完成,要麼一點都沒有執行,這樣A和B互為原子操作,一個原子操作是指,該操作對於所有的操作,包括它自己,都滿足前面描述的狀態。

鎖:java 提供了強制原子性的內建鎖機制:synchronized塊。它包括兩部分:鎖物件引用,以及這個鎖保護的程式碼塊。因為鎖使得執行緒能夠序列地(serialized)訪問它所保護的程式碼路徑,所以我們可以用鎖建立相關的協議,以保證執行緒對共享狀態的獨佔訪問。只要始終如一的遵循這些協議,就能夠確保狀態的一致性。

本文首發於個人微信公眾號:webguan ;歡迎您的關注

相關文章