Jstack線程狀態BLOCKED/TIMED_WAITING/WAITING解釋
一、線程5種狀態
-
新建狀態(New) 新創建了一個線程對象。
-
就緒狀態(Runnable) 線程對象創建後,其他線程調用了該對象的start()方法。該狀態的線程位於可運行線程池中,變得可運行,等待獲取CPU的使用權。
-
運行狀態(Running) 就緒狀態的線程獲取了CPU,執行程序代碼。
-
阻塞狀態(Blocked) 阻塞狀態是線程因為某種原因放棄CPU使用權,暫時停止運行。直到線程進入就緒狀態,才有機會轉到運行狀態。阻塞的情況分三種:
- 等待阻塞:運行的線程執行wait()方法,JVM會把該線程放入等待池中。
- 同步阻塞:運行的線程在獲取對象的同步鎖時,若該同步鎖被別的線程占用,則JVM會把該線程放入鎖池中。
- 其他阻塞:運行的線程執行sleep()或join()方法,或者發出了I/O請求時,JVM會把該線程置為阻塞狀態。當sleep()狀態超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程重新轉入就緒狀態。
-
死亡狀態(Dead):線程執行完了或者因異常退出了run()方法,該線程結束生命周期。
二、Jstack中常見的線程狀態
應用程序啟動後,我們對系統運行狀況的觀測大部分情況下是通過運行日誌。但是若某一天發現,日誌中記錄的行為與預想的不一致,此時需要進一步的系統監控該怎麽辦,Jstack是常用的排查工具,它能輸出在某一個時間,java進程中所有線程的狀態,很多時候這些狀態信息能給我們的排查工作帶來有用的線索。
- RUNNABLE 線程運行中或I/O等待
- BLOCKED 線程在等待monitor鎖(synchronized關鍵字)
- TIMED_WAITING 線程在等待喚醒,但設置了時限
- WAITING 線程在無限等待喚醒
這裏Jstack使用的關鍵字描述的線程狀態與上一節中線程不太一樣,所以可能理解上的可能會出現混淆。雖然Java中的線程一樣有上節中描述的5種狀態,但在實際情況下線程新建狀態和死亡狀態持續很短,我們也並不太關心。大多時候我們關註的是運行狀態/阻塞狀態,這裏弄清楚Jstack的輸出含義即可。下面用簡單的代碼產生出以上4中狀態。
public static void main(String[] args) { System.out.println(Utils.pid()); runnable(); // 1 // blocked(); // 2 // waiting(); // 3 // timedWaiting(); // 4 } public static String pid() { String name = ManagementFactory.getRuntimeMXBean().getName(); return name.split("@")[0]; }
這裏為了方便得到java進程id,直接使用pid()函數輸出。為了方便,我們把觀察線程固定為”main”,因為JVM還有其他線程都會存在輸出中,我們可以通過關鍵字”main”找到我們要觀察的線程。命令jstack -l [pid]。
1) 讓線程一直處於RUNNABLE
public static void runnable() { long i = 0; while (true) { i++; } }
沒什麽好解釋的,死循環即可。
2) 讓線程一直處於BLOCKED
public static void blocked() { final Object lock = new Object(); new Thread() { public void run() { synchronized (lock) { System.out.println("i got lock, but don‘t release"); try { Thread.sleep(1000L * 1000); } catch (InterruptedException e) { } } } }.start(); try { Thread.sleep(100); } catch (InterruptedException e) {} synchronized (lock) { try { Thread.sleep(30 * 1000); } catch (InterruptedException e) { } } }
主線程sleep,先讓另外一個線程拿到lock,並長期持有lock(sleep會持有鎖,wait不會)。此時主線程會BLOCK住等待lock被釋放,此時jstack的輸出可以看到main線程狀態是BLOCKED。這裏要註意的是只有synchronized這種方式的鎖(monitor鎖)才會讓線程出現BLOCKED狀態,等待ReentrantLock則不會。
3) 讓線程處於TIMED_WAITING狀態
public static void timedWaiting() { final Object lock = new Object(); synchronized (lock) { try { lock.wait(30 * 1000); } catch (InterruptedException e) { } } }
用Lock.tryLock(timeout, timeUnit),這種方式也會看到TIMED_WAITING狀態,這個狀態說明線程當前的等待一定是可超時的。
4) 讓線程處於WAITING狀態
public static void waiting() { final Object lock = new Object(); synchronized (lock) { try { lock.wait(); } catch (InterruptedException e) { } } }
無超時的等待,必須等待lock.notify()或lock.notifyAll()或接收到interrupt信號才能退出等待狀態。同理,ReentrantLock.lock()的無參方法調用,也會使線程狀態變成WAITING。
通過以上幾個最簡單的例子,讓線程達到jstack輸出中常見的幾種狀態,可以更好地理解jstack輸出。
Jstack線程狀態BLOCKED/TIMED_WAITING/WAITING解釋