1. 程式人生 > >java多執行緒狀態詳解

java多執行緒狀態詳解

java.lang.Thread類中有個內部列舉類State用來描述執行緒的各種狀態,具體如下

    public enum State {
        /**
         * 尚未啟動的執行緒的狀態。
         */
        NEW,

        /**
         * 可執行執行緒的執行緒狀態。處於可執行狀態的某一執行緒正在Java虛擬機器中執行,但它可能正在等待作業系統            * 中的其他資源,比如處理器。
         */
        RUNNABLE,

        /**
         * 受阻塞並且正在等待監視器鎖的某一執行緒的執行緒狀態。處於受阻塞狀態的某一執行緒正在等待監視器鎖,以          * 便進入一個同步程式碼塊/方法,或者在呼叫Object.wait之後再次進入同步程式碼塊/方法。
         */
BLOCKED, /** * 某一等待執行緒的執行緒狀態。某一執行緒因為呼叫下列方法之一而處於等待狀態: * <ul> * <li>不帶超時值的Object.wait</li> * <li>不帶超時值的Thread.join</li> * <li>LockSupport.park<li> * </ul> * 處於等待狀態的執行緒正等待另一個執行緒執行特定操作。 例如,已經在某一物件上呼叫了Object.wait()的 * 執行緒正等待另一個執行緒在該物件上呼叫Object.notify()或Object.notifyAll()。已經呼叫了 * Thread.join()的執行緒正在等待指定執行緒終止。 */
WAITING, /** * 具有指定等待時間的某一等待執行緒的執行緒狀態。某一執行緒因為呼叫以下帶有指定正等待時間的方法之一而處 * 於定時等待狀態: * <ul> * <li>Thread.sleep</li> * <li>帶有超時值的 Object.wait</li> * <li>帶有超時值的 Thread.join</li> * <li>LockSupport.parkNanos</li> * <li>LockSupport.parkUntil</li> * </ul> */
TIMED_WAITING, /** * 已終止執行緒的執行緒狀態。執行緒已經結束執行。 */ TERMINATED; }

下面就用程式碼呈現各種執行緒狀態

  1. NEW狀態

    package cn.cjc.multithread;
    
    public class MainTest {
       public static void main(String[] args) {
           Thread t = new Thread();
           System.out.println(t.getState());
       }
    }

    輸出結果就是NEW

  2. RUNNABLE狀態

    package cn.cjc.multithread;
    
    public class MainTest {
       public static void main(String[] args) {
           Thread t = new Thread();
           t.start();
           System.out.println(t.getState());
       }
    }

    輸出結果就是RUNNABLE,不過這樣並不是每次都能打印出RUNNABLE,也有可能打印出TERMINATED,因為main執行緒和t執行緒是併發執行的,有可能在列印執行緒狀態時t執行緒已經結束運行了。為了總能打印出RUNNABLE,讓t執行緒無限執行下去,程式碼如下

    package cn.cjc.multithread;
    
    public class MainTest {
       public static void main(String[] args) {
           Thread t = new Thread(new Runnable() {
               @Override
               public void run() {
                   while (true) ;//無限迴圈
               }
           });
           t.start();
           System.out.println(t.getState());
       }
    }

    還有一種檢視執行緒狀態的方法,就是用JVM命令檢視,對於上面的程式碼,程式會一直執行,所以可以用jstack命令dump出線程狀態,操作如下

    1)用jps命令檢視JVM程序的pid,可知pid=4069

    pid
    2)用jstack命令列印執行緒棧資訊,可以看到Thread-0的執行緒處於RUNNABLE狀態

    runnable

    需要注意的是執行緒狀態是RUNNABLE不代表該執行緒正在執行中,有可能該執行緒時間片用完,正在等待CPU,也就是處於就緒狀態。所以RUNNABLE有可能是正在執行中,也有可能是就緒狀態。

  3. BLOCKED狀態

    執行緒呼叫synchronized方法或者進入synchronized程式碼塊時,獲取不到監視器鎖而阻塞,則產生該狀態

    package cn.cjc.multithread;
    
    public class MainTest {
       public static void main(String[] args) {
           final MainTest mainTest = new MainTest();
           new Thread(new Runnable() {
               @Override
               public void run() {
                   mainTest.m1();
               }
           }, "thread-m1").start();
    
           new Thread(new Runnable() {
               @Override
               public void run() {
                   mainTest.m2();
               }
           }, "thread-m2").start();
       }
    
       public synchronized void m1() {
           System.out.println("m1");
           while (true) ;
       }
    
       public synchronized void m2() {
           System.out.println("m2");
           while (true) ;
       }
    }

    執行後用jstack命令檢視執行緒狀態發現thread-m1執行緒是RUNNABLE狀態,而thread-m2執行緒是BLOCKED (on object monitor)狀態,因為同步鎖一直被thread-m1持有,thread-m2在等待鎖

    blocked_1

    還有一種情況也會產生BLOCKED狀態,程式碼如下

    package cn.cjc.multithread;
    
    public class MainTest {
       public static void main(String[] args) throws InterruptedException {
           final MainTest mainTest = new MainTest();
           new Thread(new Runnable() {
               @Override
               public void run() {
                   mainTest.m1();
               }
           }, "thread-m1").start();
    
           new Thread(new Runnable() {
               @Override
               public void run() {
                   mainTest.m2();
               }
           }, "thread-m2").start();
    
           Thread.sleep(3000);
    
           new Thread(new Runnable() {
               @Override
               public void run() {
                   mainTest.m3();
               }
           }, "thread-m3").start();
       }
    
       public synchronized void m1() {
           System.out.println("m1---start");
           try {
               this.wait();
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           System.out.println("m1---end");
           while (true) ;
       }
    
       public synchronized void m2() {
           System.out.println("m2---start");
           try {
               this.wait();
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           System.out.println("m2---end");
           while (true) ;
       }
    
       public synchronized void m3() {
           System.out.println("m3");
           this.notifyAll();//注意不是this.notify()哦
       }
    }

    執行後用jstack命令列印如下

    blocked_2

    發現thread-m1執行緒是BLOCKED (on object monitor)狀態,這和第一種情況不是一樣嗎?其實不一樣,看此處thread-m1執行緒棧的第一行有個in Object.wait(),而第一種情況thread-m2執行緒棧的第一行是waiting for monitor entry,有啥區別?

    第二種情況產生過程是這樣:m1執行緒先持有鎖,然後釋放鎖並等待被喚醒,緊接著m2執行緒持有鎖,然後釋放鎖並等待被喚醒,m3執行緒呼叫notifyAll方法喚醒了所有等待著的執行緒,這時候m2執行緒拿到鎖並一直持有,m1執行緒因為被喚醒了,但沒有拿到鎖,所以從WAITING狀態變成了BLOCKED狀態。

    第一種情況下執行緒BLOCKED,是在Entry Set裡面,第二種情況執行緒BLOCKED,是在Wait Set裡面。

    我要說還有第三種情況的BLOCKED,會不會被打?真有呢!就是呼叫帶超時時間的Object.wait且看程式碼

    package cn.cjc.multithread;
    
    public class MainTest {
       public static void main(String[] args) {
           final MainTest mainTest = new MainTest();
           new Thread(new Runnable() {
               @Override
               public void run() {
                   mainTest.m1();
               }
           }, "thread-m1").start();
    
           new Thread(new Runnable() {
               @Override
               public void run() {
                   mainTest.m2();
               }
           }, "thread-m2").start();
       }
    
       public synchronized void m1() {
           System.out.println("m1---start");
           try {
               this.wait(30000);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           System.out.println("m1---end");
           while (true) ;
       }
    
       public synchronized void m2() {
           System.out.println("m2---start");
           try {
               this.wait(30000);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           System.out.println("m2---end");
           while (true) ;
       }
    }

    執行起來後,30s內用jstack命令檢視執行緒m1和m2的狀態都是WAITING (on object monitor),但是30s以後檢視執行緒狀態,如圖示

    blocked_3

    thread-m2執行緒處在BLOCKED狀態,但是阻塞原因第二情況是in Object.wait(),而這個是waiting for monitor entry,我以為會和第二種情況一樣(為什麼會不一樣呢?後面有時間再深入看看),納尼?這不是和第一種進入synchronized同步塊的情況一樣嗎?確實很像,但是從阻塞執行緒棧的第三行往下看就可以區分開來了。

  4. WAITING狀態

    WAITING狀態線上程棧中有兩種不同的體現,分別為WAITING (on object monitor)WAITING (parking)

    1)呼叫Object.wait()產生WAITING (on object monitor)狀態,程式碼如下

    package cn.cjc.multithread;
    
    public class MainTest {
       public static void main(String[] args) {
           final MainTest mainTest = new MainTest();
           new Thread(new Runnable() {
               @Override
               public void run() {
                   mainTest.m1();
               }
           }, "thread-m1").start();
       }
    
       public synchronized void m1() {
           System.out.println("m1---start");
           try {
               this.wait();
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           System.out.println("m1---end");
       }
    }

    執行後的執行緒棧如下

    waiting_1

    2)呼叫Thread.join產生WAITING (on object monitor)狀態,程式碼如下

    package cn.cjc.multithread;
    
    public class MainTest {
       public static void main(String[] args) throws InterruptedException {
           Thread t = new Thread(new Runnable() {
               @Override
               public void run() {
                   while (true) ;
               }
           });
           t.start();
           t.join();//等待t執行緒結束後再往下執行
           System.out.println("main---end");
       }
    }

    執行後執行緒棧如下

    waiting_2

    3)呼叫java.util.concurrent.locks.Lock#lock方法會產生WAITING (parking)狀態,程式碼如下

    package cn.cjc.multithread;
    
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class MainTest {
       private Lock lock = new ReentrantLock();
    
       public static void main(String[] args) {
           final MainTest mainTest = new MainTest();
           new Thread(new Runnable() {
               @Override
               public void run() {
                   mainTest.m1();
               }
           }, "thread-m1").start();
    
           new Thread(new Runnable() {
               @Override
               public void run() {
                   mainTest.m2();
               }
           }, "thread-m2").start();
       }
    
       public void m1() {
           lock.lock();
           try {
               System.out.println("m1---start");
               while (true) ;
           } finally {
               lock.unlock();
               System.out.println("m1---end");
           }
       }
    
       public void m2() {
           lock.lock();
           try {
               System.out.println("m2---start");
               while (true) ;
           } finally {
               lock.unlock();
               System.out.println("m2---end");
           }
       }
    }

    執行後的執行緒棧如下

    waiting_3

    可以發現thread-m1執行緒在執行中,而thread-m2執行緒沒有獲得鎖處於WAITING狀態,這個和進入synchronized方法或程式碼塊時沒有獲得鎖產生的狀態是完全不一樣的。

    另外java.util.concurrent.locks.Condition#await()方法也會產生WAITING (parking)狀態,java.util.concurrent.BlockingQueue#putjava.util.concurrent.BlockingQueue#take底層呼叫的就是此方法。其實它們底層呼叫的都是LockSupport.park方法。

  5. TIMED_WAITING狀態

    TIMED_WAITING狀態要細分的話,會有三種,分別是TIMED_WAITING (sleeping)TIMED_WAITING (on object monitor)TIMED_WAITING (parking)

    1)Thread.sleep方法會產生第一種狀態;

    2)帶超時時間的Object.wait和帶超時時間的Thread.join方法會產生第二種狀態;

    3)帶超時時間的lock.tryLock和帶超時時間的Condition.await方法會產生第三種狀態,帶超時時間的java.util.concurrent.BlockingQueue#offerjava.util.concurrent.BlockingQueue#poll的底層呼叫的就是帶超時時間的Condition.awaitNanos方法。

  6. TERMINATED狀態

    這個狀態最好理解,如程式碼

    package cn.cjc.multithread;
    
    public class MainTest {
       public static void main(String[] args) throws InterruptedException {
           Thread t = new Thread();
           t.start();
           Thread.sleep(3000);
           System.out.println(t.getState());
       }
    }

    輸出結果就是TERMINATED