線程問題怎么排查


線程狀態的定義

可見在 JDK 中定義的線程狀態總共六種,各狀態在特定條件下可以轉換,其組成了一個線程的生命周期,為了方便理解,對其狀態和轉換整理成了列表和狀態圖的形式。

狀態 描述
NEW 線程新建但是還沒有 start 的時候,即 new Thread()
RUNNABLE 調用了 Thread 的 start() 方法,此時線程可運行,但是也有可能需要等待其他操作系統資源,比如處理器資源,當獲取到處理器資源之后,則進入 RUNNING 狀態
BLOCKED 當進入同步代碼塊時,如果需要等待獲取鎖,那么就會被阻塞進入該狀態
WAITING 由於執行了 Object.wait()、`Thread.join()、LockSupport.park() 進入了等待狀態
TIMED_WAITING 由於執行了 Thread.sleep(long)、Object.wait(long)、Thread.join(long)、LockSupport.parkNanos、LockSupport.parkUntil,進入了有限時長的等待狀態
TERMINATED 線程 run 方法執行結束

線程運行的幾個概念

  • 臨界區

臨界區用來表示一種公共資源或者說是共享數據,可以被多個線程使用。但是每一次,只能有一個線程使用它,一旦臨界區資源被占用,其他線程要想使用這個資源,就必須等待。

  • 死鎖

死鎖是進程死鎖的簡稱,是指多個進程循環等待他方占有的資源而無限的僵持下去的局面。

  • 活鎖

假設有兩個線程1、2,它們都需要資源 A/B,假設1號線程占有了 A 資源,2號線程占有了 B 資源;由於兩個線程都需要同時擁有這兩個資源才可以工作,為了避免死鎖,1號線程釋放了 A 資源占有鎖,2號線程釋放了 B 資源占有鎖;此時 AB 空閑,兩個線程又同時搶鎖,再次出現上述情況,此時發生了活鎖。

簡單類比,電梯遇到人,一個進的一個出的,對面占路,兩個人同時往一個方向讓路,來回重復,還是堵着路。

如果線上應用遇到了活鎖問題,恭喜你中獎了,這類問題比較難排查。

  • 飢餓

飢餓是指某一個或者多個線程因為種種原因無法獲得所需要的資源,導致一直無法執行。

線程問題排查

在多線程程序中,如果出現的問題是數據異常類的問題,比較難排查需要一點點的檢查代碼。如果說是資源類的問題排查起來相對來說比較簡單。常用的命令就是 top/jps 以及 ps 定位出是哪個進程。然后通過 jstack 命令打出這個進程的全部線程堆棧,接下來就是分析打印的堆棧信息了。在堆棧信息里面打印的線程狀態有:

 死鎖,Deadlock(重點關注)
 執行中,Runnable  
 等待資源,Waiting on condition(重點關注)
 等待獲取監視器,Waiting on monitor entry(重點關注)
 暫停,Suspended
 對象等待中,Object.wait() 或 TIMED_WAITING
 阻塞,Blocked(重點關注) 
 停止,Parked

可能存在的情況有:

  • 線程狀態為“Runnable”。

該狀態表示線程具備所有運行條件,在運行隊列中准備操作系統的調度,或者正在運行。

  • 線程狀態為“waiting for monitor entry”。

意味着它在等待進入一個臨界區,所以它在“Entry Set”隊列中等待。

此時線程狀態一般都是 Blocked:java.lang.Thread.State: BLOCKED (on object monitor)。

  • 線程狀態為“waiting on condition”。

說明它在等待另一個條件的發生,來把自己喚醒,或者干脆它是調用了 sleep(N)。此時線程狀態大致為以下幾種:

(1) java.lang.Thread.State: WAITING (parking):一直等那個條件發生;

(2) java.lang.Thread.State: TIMED_WAITING (parking或sleeping):定時的,那個條件不到來,也將定時喚醒自己。

  • 如果大量線程在“waiting for monitor entry”。

可能是一個全局鎖阻塞住了大量線程。

如果短時間內打印的 thread dump 文件反映,隨着時間流逝,waiting for monitor entry 的線程越來越多,沒有減少的趨勢,可能意味着某些線程在臨界區里呆的時間太長了,以至於越來越多新線程遲遲無法進入臨界區。

  • 如果大量線程在“waiting on condition”:

可能是它們又跑去獲取第三方資源,尤其是第三方網絡資源,遲遲獲取不到 Response,導致大量線程進入等待狀態。

所以如果你發現有大量的線程都處在 Wait on condition,從線程堆棧看,正等待網絡讀寫,這可能是一個網絡瓶頸的征兆,因為網絡阻塞導致線程無法執行。

  • 線程狀態為“in Object.wait()”:

說明它獲得了監視器之后,又調用了 java.lang.Object.wait() 方法。

每個 Monitor在某個時刻,只能被一個線程擁有,該線程就是 “Active Thread”,而其它線程都是 “Waiting Thread”,分別在兩個隊列 “ Entry Set”和 “Wait Set”里面等候。在 “Entry Set”中等待的線程狀態是 “Waiting for monitor entry”,而在 “Wait Set”中等待的線程狀態是 “in Object.wait()”。

當線程獲得了 Monitor,如果發現線程繼續運行的條件沒有滿足,它則調用對象(一般就是被 synchronized 的對象)的 wait() 方法,放棄了 Monitor,進入“Wait Set”隊列。

此時線程狀態大致為以下幾種:

java.lang.Thread.State: TIMED_WAITING (on object monitor);

java.lang.Thread.State: WAITING (on object monitor);

線程問題排查工具

cpu過高分析原因,到代碼級別

解決過程:
1,根據top命令,發現PID為2633的Java進程占用CPU高達300%,出現故障。
2,找到該進程后,如何定位具體線程或代碼呢,首先顯示線程列表,並按照CPU占用高的線程排序:

[root@localhost logs]# ps -mp 2633 -o THREAD,tid,time | sort -rn

顯示結果如下:

USER     %CPU PRI SCNT WCHAN  USER SYSTEM   TID     TIME
root     10.5  19    - -         -      -  3626 00:12:48
root     10.1  19    - -         -      -  3593 00:12:16

找到了耗時最高的線程3626,占用CPU時間有12分鍾了!
將需要的線程ID轉換為16進制格式:

[root@localhost logs]# printf "%x\n" 3626
e18

最后打印線程的堆棧信息:

[root@localhost logs]# jstack 2633 |grep e18 -A 30

腳本 show-busy-java-threads ,自動化上面的排查過程,

一鍵輸出 javaCPU消耗高的線程:

https://github.com/oldratlee/useful-scripts/blob/master/docs/java.md#-show-busy-java-threads

top命令查看線程cpu

//間隔1秒(-d 1),輸出一次(-n 1)
top -Hp pid -d 1 -n 1

//打印System_Server進程各個線程的Java調用棧,根據線程狀態及調用棧來更進一步定位問題點
kill -3 pid 

掃描二維碼,關注公眾號“猿必過”

file

回復 “面試題” 自行領取吧。

微信群交流討論,請添加微信號:zyhui98,備注:面試題加群

本文由猿必過 YBG 發布

禁止未經授權轉載,違者依法追究相關法律責任

如需授權可聯系:zhuyunhui@yuanbiguo.com


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM