1. 程式人生 > 程式設計 >Java執行緒狀態及切換、關閉執行緒的正確姿勢分享

Java執行緒狀態及切換、關閉執行緒的正確姿勢分享

前言

在講執行緒之前有必要討論一下程序的定義:程序是程式在一個數據集合上執行的過程,它是系統進行資源分配和排程的一個獨立單位。程序實體由程式段, 資料段 PCB(程序控制塊)組成。執行緒又是什麼?執行緒可以看做輕量級程序,執行緒是程序的執行單元,是程序排程的基本單位

本文將詳細介紹關於Java執行緒狀態及切換、關閉執行緒的相關內容,下面話不多說了,來一起看看詳細的介紹吧

1、執行緒狀態及切換

Java中的執行緒有六種狀態,使用執行緒Thread內的列舉類來實現,如下,我對每個狀態都進行了一定的解釋。

public enum State {
  /** 表示一個執行緒還沒啟用(即未呼叫start方法)*/
  NEW,/**
   * JVM中執行的執行緒都是處於這個狀態的,但是處於這個狀態不一定在JVM中執行,
   * 也就是說,只有這個狀態有資格被JVM排程從而獲得時間片執行。
   */
  RUNNABLE,/**
   * 執行緒在等待獲取鎖資源從而進入阻塞狀態,
   * 在這個狀態中,其一直監視鎖的動態,隨時準備搶佔鎖
   * 若獲得鎖資源,重新進入RUNNABLE狀態
   */
  BLOCKED,/**
   * 當呼叫Object.wait、Thread.join或者LockSupport類的park方法的時候,執行緒進入此狀態,
   * 該狀態若無其他執行緒主動喚醒,則無期限的等待。
   * 喚醒的方法包括:Object.notify(喚醒隨機一個)、Object.notifyAll(喚醒全部執行緒),
   * 被喚醒的執行緒重新進入RUNNABLE狀態
   */
  WAITING,/**
   * 同WAITING狀態,不過不同的是呼叫的方法加上了時間的限制,
   * 例如:Object.wait(10)、Thread.sleep(10)、Thread.join(10)、LockSupport.parkNanos(10)、LockSupport.parkUntil(10)這些方法
   * 喚醒的方法有兩種:
   *  1、時間過期。
   *  2、其他執行緒呼叫了notify或者notifyAll
   * 喚醒之後同樣進入RUNNABLE狀態
   */
  TIMED_WAITING,/** 執行緒的終點(正常死亡或者被終止)*/
  TERMINATED;
 }

除了NEW和TERMINATED之外,其他的狀態都是可以相互轉換的,其轉換過程如下圖所示

這裡特別講一下RUNNABLE狀態,在這個狀態中執行緒並不一定在執行程式,只有被JVM排程的執行緒才能獲得執行的時間片,並且只有這個狀態的執行緒才能夠獲得時間片,換句話說,被JVM排程並且獲得時間片是隻屬於處於RUNNABLE狀態執行緒的權利。為了便於理解,可以將RUNNABLE分成Runnable和Running兩個狀態(當然,你也可以換成其他的,這裡我只是自己好理解),那麼上面的執行緒轉換圖就轉變成了下面這樣(參考《Java併發程式設計的藝術》中的執行緒狀態圖):

關於執行緒狀態轉換的例子,可以通過下面的程式碼加深理解

public class Test {
 public static void main(String[] args) {
  Test test = new Test();
  // 1.NEW狀態
  Thread thread = new Thread(() -> {
   // 3.進行test物件鎖的爭奪,若搶到鎖則繼續執行,否則進入BLOCKED狀態監控該鎖,重新獲得後進入RUNNABLE 
   synchronized (test) {
    try {
     // 4.進入TIMED_WAITING狀態,100ms後重新進入RUNNABLE狀態爭奪時間片 
     Thread.sleep(100);
     // 5.重新獲得時間片之後,進入WAITING狀態 
     test.wait();
    } catch (InterruptedException e) {
     e.printStackTrace();
    }
   }
   // 6.正常run()方法執行完畢後執行緒結束,進入TERMINATED 
  });
  // 2.呼叫start()方法,執行緒進入RUNNABLE狀態
  thread.start();
 }
}

注:程式碼執行的順序為註釋的序號

2、正確的結束一個執行緒

在上面的例子中我們看到執行緒的run方法正常執行完畢之後執行緒就正常死亡進入TERMINATED狀態了,那麼如果我們有中途停止執行緒的需求,我們應該如何正確的結束一個執行緒呢?

使用interrupt()方法:線上程內部,其定義了一個變數來標識當前執行緒是否處於被打斷狀態,呼叫interrupt()方法則使這個狀態變為true。我們採用這個方法加異常處理的方式來結束一個執行緒。

  public static void main(String[] args) {
  Thread thread = new Thread(() -> {
   try {
    Thread.sleep(1);
   } catch (InterruptedException e) {
    e.printStackTrace();
    // 這裡的return是必須的,原因後面說明
    return;
   }
   System.err.println("thread interrupt test...");
  });
  thread.start();
  thread.interrupt();
  System.out.println("main thread end...");
 }

// 結果圖:異常後面的語句不會列印

  這裡關於執行緒中的打斷標識變數(之後以interrupt稱)需要說明的是,在特定的情況下其狀態會被重置。

   1、執行緒內部在catch了異常了之後interrupt的狀態會被重置為false。

2、執行緒呼叫了Thread.interrupted()方法之後,interrupt的狀態會被重置為false。如果需要判斷執行緒是否中斷的話可以使用物件方法isInterrupted(),此方法不會重置。

所以在剛才的程式碼中需要加入return來結束執行緒,否則的話執行緒還是會繼續往下執行,如下圖


使用isInterrupted()實現:

public static void main(String[] args) throws InterruptedException {
 Thread thread = new Thread(() -> {
  while (!Thread.currentThread().isInterrupted()) {
   int k = 0;
   while (k++ < 10) {
    System.out.println("do something..." + k);
   }
  }
 System.err.println("thread end...");
 });
 thread.start();
 Thread.sleep(1);
 // 主執行緒流程執行完了,需要停止執行緒
 thread.interrupt();
}

使用標識位來實現:定義一個變數標識執行緒是否終止,若終止了則退出run方法。跟上面isInterrupted()的實現一樣,不過換成了volatile變數而已。

public class Test {

 public static volatile boolean interrupted = false;

 public static void main(String[] args) throws InterruptedException {
  Thread thread = new Thread(() -> {
   while (!interrupted) {
    int k = 0;
    while (k++ < 10) {
     if (interrupted) {
      System.err.println("thread invoke end....");
      return;
     }
     System.out.println("do something..." + k);
    }
   }
  System.err.println("thread end...");
  });
  thread.start();
  Thread.sleep(1);
  // 主執行緒流程執行完了,需要停止執行緒
  interrupted = true;
 }
}
// 結果圖

 stop()方法——不正確的執行緒中斷方法

   線上程提供的方法中還有一個方法可以強制關閉執行緒——stop()。這個方法可以說是相當的霸道,給人一種“我不管,我就是要你現在立刻死亡(指執行緒)”的感覺,並且其還會釋放執行緒所有的鎖資源,這樣可能會導致出現數據不一致從而出現執行緒不安全的情況,如下面例子。

public class Test {

  public static volatile boolean flag = false;
  public int state = 0;

  public static void main(String[] args) throws InterruptedException {
   Test test = new Test();
   Thread thread = new Thread(() -> {
    synchronized (test) {
     try {
      test.state = 1;
      Thread.sleep(100);
      if (flag) {
       test.state = 2;
      }
      System.err.println("thread execute finished...");
     } catch (InterruptedException e) {
      e.printStackTrace();
     }
    }
   });
   thread.start();
   Thread.sleep(1);
   thread.stop();
   flag = true;
   System.out.println("state狀態:" + test.state);
  }
}
// 在這段程式碼中,進入執行緒時預設將state賦為1,接著過一段時間後如果觸發了特定條件則把state賦為2,但是在特定條件觸發之前,執行緒就被終止掉了,這個特定條件雖然符合但卻沒辦法執行,從而導致資料的不一致。
// 結果圖


所以,我們應該採用上面兩種正確的方式而不是stop()來中止執行緒。此外,stop()方法若線上程start()之前執行,那麼線上程啟動的時候就會立即死亡。

若有不對之處,望各位不吝指教(反正免費,對吧)。

總結

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,謝謝大家對我們的支援。