一、線程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進程中所有線程的狀態,很多時候這些狀態信息能給我們的排查工作帶來有用的線索。
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輸出。