1. 程式人生 > 實用技巧 >轉 Java執行緒的6種狀態分析

轉 Java執行緒的6種狀態分析

https://blog.csdn.net/u010285974/article/details/107614693

想起來寫一下Java執行緒狀態,還是源起與最近的一次問題定位,當時碰到一個偶先超時的問題,使用jstack命令打印出堆疊資訊之後,例如


"transport-vert.x-eventloop-thread-11" #37 prio=5 os_prio=0 tid=0x00007f628d0f8800 nid=0x2a32 runnable [0x00007f62631f8000]
java.lang.Thread.State: RUNNABLE
at sun.nio.ch.EPollArrayWrapper.epollWait(Native Method)

at sun.nio.ch.EPollArrayWrapper.poll(EPollArrayWrapper.java:269)
at sun.nio.ch.EPollSelectorImpl.doSelect(EPollSelectorImpl.java:93)
at sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:86)
- locked <0x00000006f3cb4310> (a io.netty.channel.nio.SelectedSelectionKeySet)
- locked <0x00000006f3cb43d8> (a java.util.Collections$UnmodifiableSet)
- locked <0x00000006f3cb42c8> (a sun.nio.ch.EPollSelectorImpl)
at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:97)
at io.netty.channel.nio.SelectedSelectionKeySetSelector.select(SelectedSelectionKeySetSelector.java:62)
at io.netty.channel.nio.NioEventLoop.select(NioEventLoop.java:755)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:410)
at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:884)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.lang.Thread.run(Thread.java:748)

"soul-netty-nio-4" #72 daemon prio=5 os_prio=0 tid=0x00000000024d0000 nid=0x579f runnable [0x00007fdecf301000]
java.lang.Thread.State: RUNNABLE
at sun.nio.ch.EPollArrayWrapper.epollWait(Native Method)
at sun.nio.ch.EPollArrayWrapper.poll(EPollArrayWrapper.java:269)
at sun.nio.ch.EPollSelectorImpl.doSelect(EPollSelectorImpl.java:93)
at sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:86)
- locked <0x000000008130eed8> (a io.netty.channel.nio.SelectedSelectionKeySet)
- locked <0x000000008130ffd8> (a java.util.Collections$UnmodifiableSet)
- locked <0x000000008130ff00> (a sun.nio.ch.EPollSelectorImpl)
at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:97)
at io.netty.channel.nio.SelectedSelectionKeySetSelector.select(SelectedSelectionKeySetSelector.java:62)
at io.netty.channel.nio.NioEventLoop.select(NioEventLoop.java:757)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:412)
at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:897)
at java.lang.Thread.run(Thread.java:748)

通過定位執行緒的狀態,找到了錯誤的原因,也稍微費了些勁,所以想到把執行緒的一些狀態再這裡面總結一下

執行緒的6種狀態

Java執行緒Threadpackage java.lang;中可以找到,通過原始碼,我們可以看到其狀態有如下6種

  • NEW
  • RUNNABLE
  • BLOCKED
  • WAITING
  • TIMED_WAITING
  • TERMINATED

下面分別解釋一下各種狀態

NEW

顧名思義,這個狀態,只存在於執行緒剛建立,未start之前,例如

  1. MyThread thread = new MyThread();
  2. System.out.println(thread.getState());

此時打印出來的狀態就是NEW

RUNNABLE

這個狀態的執行緒,其正在JVM中執行,但是這個"執行",不一定是真的在執行, 也有可能是在等待CPU資源。所以,在網上,有人把這個狀態區分為READY和RUNNING兩個,一個表示的start了,資源一到位隨時可以執行,另一個表示真正的執行中,例如

  1. MyThread thread = new MyThread(lock);
  2. thread.start();
  3. System.out.println(thread.getState());

BLOCKED

這個狀態,一般是執行緒等待獲取一個鎖,來繼續執行下一步的操作,比較經典的就是synchronized關鍵字,這個關鍵字修飾的程式碼塊或者方法,均需要獲取到對應的鎖,在未獲取之前,其執行緒的狀態就一直未BLOCKED,如果執行緒長時間處於這種狀態下,我們就是當心看是否出現死鎖的問題了。例如

  1. public class MyThread extends Thread {
  2. private byte[] lock = new byte[0];
  3. public MyThread(byte[] lock) {
  4. this.lock = lock;
  5. }
  6. @Override
  7. public void run() {
  8. synchronized (lock){
  9. try {
  10. Thread.sleep(10000);
  11. } catch (InterruptedException e) {
  12. e.printStackTrace();
  13. }
  14. System.out.println("done");
  15. }
  16. }
  17. }

  1. public static void main(String[] args) throws InterruptedException {
  2. byte[] lock = new byte[0];
  3. MyThread thread1 = new MyThread(lock);
  4. thread1.start();
  5. MyThread thread2 = new MyThread(lock);
  6. thread2.start();
  7. Thread.sleep(1000);//等一會再檢查狀態
  8. System.out.println(thread2.getState());
  9. }

此時我們看到的輸出的第二個執行緒的狀態就是BLOCKED

WAITING

一個執行緒會進入這個狀態,一定是執行了如下的一些程式碼,例如

  • Object.wait()
  • Thread.join()
  • LockSupport.park()
    當一個執行緒執行了Object.wait()的時候,它一定在等待另一個執行緒執行Object.notify()或者Object.notifyAll()。
    或者一個執行緒thread,其在主執行緒中被執行了thread.join()的時候,主執行緒即會等待該執行緒執行完成。當一個執行緒執行了LockSupport.park()的時候,其在等待執行LockSupport.unpark(thread)。當該執行緒處於這種等待的時候,其狀態即為WAITING。需要關注的是,這邊的等待是沒有時間限制的,當發現有這種狀態的執行緒的時候,若其長時間處於這種狀態,也需要關注下程式內部有無邏輯異常。例如
    LockSupport.park()

  1. public class MyThread extends Thread {
  2. private byte[] lock = new byte[0];
  3. public MyThread(byte[] lock) {
  4. this.lock = lock;
  5. }
  6. @Override
  7. public void run() {
  8. LockSupport.park();
  9. }
  10. }

  1. public static void main(String[] args) throws InterruptedException {
  2. byte[] lock = new byte[0];
  3. MyThread thread1 = new MyThread(lock);
  4. thread1.start();
  5. Thread.sleep(100);
  6. System.out.println(thread1.getState());
  7. LockSupport.unpark(thread1);
  8. Thread.sleep(100);
  9. System.out.println(thread1.getState());
  10. }

輸出WAITING和TERMINATED

Object.wait()

  1. public class MyThread extends Thread {
  2. private byte[] lock = new byte[0];
  3. public MyThread(byte[] lock) {
  4. this.lock = lock;
  5. }
  6. @Override
  7. public void run() {
  8. synchronized (lock){
  9. try {
  10. lock.wait(); //wait並允許其他執行緒同步lock
  11. } catch (InterruptedException e) {
  12. e.printStackTrace();
  13. }
  14. }
  15. }
  16. }

  1. public static void main(String[] args)
  2. throws InterruptedException {
  3. byte[] lock = new byte[0];
  4. MyThread thread1 = new MyThread(lock);
  5. thread1.start();
  6. Thread.sleep(100);
  7. System.out.println(thread1.getState()); //這時候執行緒狀態應為WAITING
  8. synchronized (lock){
  9. lock.notify(); //notify通知wait的執行緒
  10. }
  11. Thread.sleep(100);
  12. System.out.println(thread1.getState());
  13. }

輸出WAITING和TERMINATED

Thread.join()

  1. public class MyThread extends Thread {
  2. private byte[] lock = new byte[0];
  3. public MyThread(byte[] lock) {
  4. this.lock = lock;
  5. }
  6. @Override
  7. public void run() {
  8. try {
  9. Thread.sleep(10000);
  10. } catch (InterruptedException e) {
  11. e.printStackTrace();
  12. }
  13. }
  14. }

  1. public class MyThread1 extends Thread {
  2. Thread thread;
  3. public MyThread1(Thread thread) {
  4. this.thread = thread;
  5. }
  6. @Override
  7. public void run() {
  8. try {
  9. thread.join();
  10. } catch (InterruptedException e) {
  11. e.printStackTrace();
  12. }
  13. }
  14. }

  1. public class Main {
  2. public static void main(String[] args)
  3. throws InterruptedException {
  4. byte[] lock = new byte[0];
  5. MyThread thread = new MyThread(lock);
  6. thread.start();
  7. MyThread1 thread1 = new MyThread1(thread);
  8. thread1.start();
  9. Thread.sleep(100);
  10. System.out.println(thread1.getState());
  11. }
  12. }

輸出為WAITING

TIMED_WAITING

這個狀態和WAITING狀態的區別就是,這個狀態的等待是有一定時效的,即可以理解為WAITING狀態等待的時間是永久的,即必須等到某個條件符合才能繼續往下走,否則執行緒不會被喚醒。但是TIMED_WAITING,等待一段時間之後,會喚醒執行緒去重新獲取鎖。當執行如下程式碼的時候,對應的執行緒會進入到TIMED_WAITING狀態

  • Thread.sleep(long)
  • Object.wait(long)
  • Thread.join(long)
  • LockSupport.parkNanos()
  • LockSupport.parkUntil()

程式碼示例
Thread.sleep

  1. public class MyThread3 extends Thread {
  2. @Override
  3. public void run() {
  4. try {
  5. Thread.sleep(10000);
  6. } catch (InterruptedException e) {
  7. e.printStackTrace();
  8. }
  9. }
  10. }

  1. Thread thread = new MyThread3();
  2. thread.start();
  3. Thread.sleep(100);
  4. System.out.println(thread.getState());

輸出為TIMED_WAITING

Object.wait

  1. public class MyThread4 extends Thread {
  2. private Object lock;
  3. public MyThread4(Object lock) {
  4. this.lock = lock;
  5. }
  6. @Override
  7. public void run() {
  8. synchronized (lock){
  9. try {
  10. lock.wait(1000);//注意,此處1s之後執行緒醒來,會重新嘗試去獲取鎖,如果拿不到,後面的程式碼也不執行
  11. } catch (InterruptedException e) {
  12. e.printStackTrace();
  13. }
  14. System.out.println("lock end");
  15. }
  16. }
  17. }

  1. byte[] lock = new byte[0];
  2. MyThread4 thread = new MyThread4(lock);
  3. thread.start();
  4. Thread.sleep(100);
  5. System.out.println(thread.getState());
  6. Thread.sleep(2000);
  7. System.out.println(thread.getState());

輸出
TIMED_WAITING
lock end
TERMINATED

其餘方法類似

TERMINATED
這個狀態很好理解,即為執行緒執行結束之後的狀態

狀態之間的切換

執行緒狀態切換.png

以上這張圖,能夠較好的說明執行緒之前的狀態切換

借鑑

https://blog.csdn.net/pange1991/article/details/53860651