來自:孤獨煙(微訊號:zrj_guduyan)
引言
今天在網上看到了一個圖片,嗯,似乎給自己的未來找到了方向:
嗯,開始我們的正題。今天我們來講講java中的死鎖問題,大致分為下麵三個小點
- 如何檢測死鎖
- 如何預防死鎖
- 隱蔽的死鎖
正文
如何檢測死鎖
首先,我們先明白在什麼情況下會懷疑是死鎖?
簡單,就是程式沒有響應的時候。其實排查步驟和《談談線上CPU100%排查套路》是類似的。但是有一個區別,在死鎖的情況下,cpu不會跑滿。
也就是說,當你發現程式沒有響應,cpu佔用率又不是特別高的時候,第一反應就是應該是死鎖。
那麼,死鎖發生的情形也很簡單,如下圖所示,
我們準備一段程式,模擬一下
排查步驟也很簡單,執行下麵的jps
命令看看當前執行任務的行程號
D:Java>jps
22320 Launcher
27488
25612 Jps
6700 TestDeadLock
然後用jstack
命令打印出6700這個行程中,執行緒的資訊
D:Java>jstack 6700
輸出結果中,有這麼一段,就能看到死鎖執行緒資訊
多嘴一句,在輸出結果中你能看到執行緒的各種狀態,如下所示
初始狀態
實現Runnable介面和繼承Thread可以得到一個執行緒類,new一個實體出來,執行緒就進入了初始狀態。
就緒狀態
就緒狀態只是說你資格執行,排程程式沒有挑選到你,你就永遠是就緒狀態。
呼叫執行緒的start()方法,此執行緒進入就緒狀態。
當前執行緒sleep()方法結束,其他執行緒join()結束,等待使用者輸入完畢,某個執行緒拿到物件鎖,這些執行緒也將進入就緒狀態。
當前執行緒時間片用完了,呼叫當前執行緒的yield()方法,當前執行緒進入就緒狀態。
鎖池裡的執行緒拿到物件鎖後,進入就緒狀態。
執行中狀態
執行緒排程程式從可執行池中選擇一個執行緒作為當前執行緒時執行緒所處的狀態。這也是執行緒進入執行狀態的唯一一種方式。
阻塞狀態
阻塞狀態是執行緒阻塞在進入synchronized關鍵字修飾的方法或程式碼塊(獲取鎖)時的狀態。
等待
處於這種狀態的執行緒不會被分配CPU執行時間,它們要等待被顯式地喚醒,否則會處於無限期等待的狀態。
超時等待
處於這種狀態的執行緒不會被分配CPU執行時間,不過無須無限期等待被其他執行緒顯示地喚醒,在達到一定時間後它們會自動喚醒。
終止狀態
當執行緒的run()方法完成時,或者主執行緒的main()方法完成時,我們就認為它終止了。這個執行緒物件也許是活的,但是,它已經不是一個單獨執行的執行緒。執行緒一旦終止了,就不能復生。
在一個終止的執行緒上呼叫start()方法,會丟擲java.lang.IllegalThreadStateException異常。
如何預防死鎖
儘量保證加鎖順序是一樣的
例如有A,B,C三把鎖。
Thread 1的加鎖順序為A、B、C這樣的。
Thread 2的加鎖順序為A、C
這樣就不會死鎖。
如果Thread2的加鎖順序為B、A或者C、A這樣順序就不一致了,就會出現死鎖問題。
儘量用超時放棄機制
Lock介面提供了tryLock(long time, TimeUnit unit)
方法,該方法可以按照固定時長等待鎖,因此執行緒可以在獲取鎖超時以後,主動釋放之前已經獲得的所有的鎖。可以避免死鎖問題
隱蔽的死鎖
因為我在生產上碰到過幾次死鎖,執行緒日誌裡是看不到死鎖資訊的。一般這種情況下都是直接看執行緒棧的內容,自己分析。
例如下麵這個併發載入類導致的死鎖
程式碼很簡單,準備兩個執行緒並行載入,在A類中載入B類。而在B類中載入A類。就會導致死鎖,此時按上述步驟是看不到死鎖資訊的。你只能看到下麵這樣的資訊
執行緒都是處在Runable
狀態的,看不到死鎖資訊。
原因也很簡單:
static
程式碼裡頭的內容會被編譯放到clinit
方法裡。JVM執行clinit時會給clinit加上鎖,防止多執行緒併發執行。兩個執行緒在初始化的時候,先各自加上clinit鎖。然後在透過Class.forName去載入對方,因此都想獲取對方要執行clinit的鎖,因此死鎖就此發生。
總結
本文講了java中死鎖相關知識,希望大家有所收穫!
Thread類實體作為監聽物件,作為監聽物件的執行緒執行完會被底層執行喚醒通知,以這個執行緒實體作為監聽且阻塞時,這個時候就無需顯式呼叫通知喚醒操作了。具體體現是執行緒的join方法的實現,裡面程式碼並沒有使用notify/notifyAll而是由OS在執行緒執行完呼叫。
朋友會在“發現-看一看”看到你“在看”的內容