哲學家問題
一提到死鎖,很多人都會想到哲學家問題。假設有5個哲學家,圍坐在一張圓桌旁,哲學家只做兩件事,喫飯和思考。喫飯的時候需要使用兩隻筷子,但是每個哲學家面前只放了一支筷子,如果想喫飯,必須借用旁邊哲學家的筷子。
這個時候如何管理每個哲學家喫飯與思考的時機和順序就變得很重要。如果每個哲學家,拿起自己面前的筷子的時候,發現旁邊的筷子不可用時,並不是釋放手裏筷子,而是死等旁邊的筷子,就會發生死鎖。所有哲學家都會因爲等待對方的筷子而餓死。
死鎖抽象模型
多個線程相互持有彼此正在等待的鎖而又不釋放自己已經持有的鎖時就會發生死鎖現象。對於上面的哲學家問題,筷子就是鎖,而每位哲學家就是一個線程。
如果非要對死鎖的場景做個簡單的分類的話,那麼大概可以分爲如下幾類。
鎖順序死鎖
如果一個線程需要持有兩把鎖才能幹活。比如線程A先拿到1號鎖,然後再拿2號鎖,而這個時候線程B搶先持有了2號鎖,而等待持有1號鎖。這個時候將造成死鎖,造成這種死鎖的原因是因爲兩個線程獲取鎖的順序錯亂了,如果都是按照先拿1號鎖,在拿2號鎖的順序執行,就不會發生死鎖情況。
資源死鎖
假設一個任務需要對兩個數據庫進行連接,兩個數據庫的連接對象都從對應的數據庫連接池中獲取。當線程A持有數據庫1的連接,等待數據庫2的連接,而線程B持有數據庫2的連接,而等待數據庫1的鏈接,如果任意一個數據庫鏈接池資源不足,那麼也將發生死鎖現象。
線程飢餓
還有一種情況也會發生線程阻塞,假設我們定義了只有一個線程的線程池,但是我們提交了2個任務,第一個任務依賴於第二任務,但是因爲第二個任務進入了等待隊列(只有一個線程執行任務)。所以整個程序將會被阻塞住。
如何避免死鎖
通過上面對死鎖問題的產生原因進行分析,我們大概可以想到有那麼幾種方式可以避免死鎖。第一種方式就是避免持有多個鎖,但是這種情況只適合比較簡單的業務場景。第二種方式就是使用帶有超時時間的顯示鎖Lock,在規定的時間內獲取不到相應的鎖資源時則自動釋放已經持有的鎖資源,這樣就可以避免長期持有鎖而形成死鎖。
結束
死鎖問題很難被發現,即便你經歷了嚴格的測試。死鎖問題往往發生在線上高併發的場景下。所以各位在寫併發程序的時候,一定要仔細分析業務需求及自己實現的代碼邏輯。