線程問題怎麼排查

線程狀態的定義

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

狀態 描述
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 方法執行結束

線程運行的幾個概念

  • 臨界區

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

  • 死鎖

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

  • 活鎖

假設有兩個線程一、2,它們都須要資源 A/B,假設1號線程佔有了 A 資源,2號線程佔有了 B 資源;因爲兩個線程都須要同時擁有這兩個資源才能夠工做,爲了不死鎖,1號線程釋放了 A 資源佔有鎖,2號線程釋放了 B 資源佔有鎖;此時 AB 空閒,兩個線程又同時搶鎖,再次出現上述狀況,此時發生了活鎖。web

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

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

  • 飢餓

飢餓是指某一個或者多個線程由於種種緣由沒法得到所須要的資源,致使一直沒法執行。網絡

線程問題排查

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

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

可能存在的狀況有:工具

  • 線程狀態爲「Runnable」。

該狀態表示線程具有全部運行條件,在運行隊列中準備操做系統的調度,或者正在運行。ui

  • 線程狀態爲「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

相關文章
相關標籤/搜索