1. 程式人生 > >Jstack線程狀態BLOCKED/TIMED_WAITING/WAITING解釋

Jstack線程狀態BLOCKED/TIMED_WAITING/WAITING解釋

getname 技術分享 ted unit ring 占用 什麽 日誌 ava

一、線程5種狀態

  1. 新建狀態(New) 新創建了一個線程對象。

  2. 就緒狀態(Runnable) 線程對象創建後,其他線程調用了該對象的start()方法。該狀態的線程位於可運行線程池中,變得可運行,等待獲取CPU的使用權。

  3. 運行狀態(Running) 就緒狀態的線程獲取了CPU,執行程序代碼。

  4. 阻塞狀態(Blocked) 阻塞狀態是線程因為某種原因放棄CPU使用權,暫時停止運行。直到線程進入就緒狀態,才有機會轉到運行狀態。阻塞的情況分三種:

    • 等待阻塞:運行的線程執行wait()方法,JVM會把該線程放入等待池中。
    • 同步阻塞:運行的線程在獲取對象的同步鎖時,若該同步鎖被別的線程占用,則JVM會把該線程放入鎖池中。
    • 其他阻塞:運行的線程執行sleep()或join()方法,或者發出了I/O請求時,JVM會把該線程置為阻塞狀態。當sleep()狀態超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程重新轉入就緒狀態。
  5. 死亡狀態(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輸出。

Jstack線程狀態BLOCKED/TIMED_WAITING/WAITING解釋