JAVA中的執行緒世界

eacape發表於2022-03-10

程式與執行緒

  • 程式是程式向作業系統申請資源(如記憶體空間和檔案控制程式碼)的基本單位.
  • 執行緒是程式中可獨立執行的最小單位.

JAVA執行緒API

  1. 在Java中建立一個執行緒就是建立一個Thread類的例項。
  2. 每個執行緒都有其要執行的任務.執行緒任務的處理邏輯是在Thread類的run方法中實現或進行呼叫的,因此run方法相當於執行緒任務邏輯處理的入口方法,它由java虛擬機器在執行相應的執行緒時進行呼叫,而不是由應用程式碼進行呼叫。
  3. Thread類的start方法是用於啟動相關執行緒的,啟動一個執行緒的實質是請求java虛擬機器執行相應的執行緒,而這個執行緒具體什麼時候執行是由執行緒排程器決定的.因此,雖然start方法被執行了,但是並不意味著這個執行緒就開始執行了,它可能稍後執行,也可能永遠不執行.
  4. Thread中常用的兩個構造器是:Thread()和Thread(Runnable target),這兩種建立執行緒的方式如下:

    1. 定義Thread類子類的方式建立執行緒

      public class WelcomeApp {
      
        public static void main(String[] args) {
          // 建立執行緒
          Thread welcomeThread = new WelcomeThread();
      
          // 啟動執行緒
          welcomeThread.start();
      
          // 輸出“當前執行緒”的執行緒名稱
          System.out.printf("1.Welcome! I'm %s.%n", Thread.currentThread().getName());
        }
      }
      
      // 定義Thread類的子類
      class WelcomeThread extends Thread {
      
        // 在該方法中實現執行緒的任務處理邏輯
        @Override
        public void run() {
          System.out.printf("2.Welcome! I'm %s.%n", Thread.currentThread().getName());
        }
      }
      
    2. 實現Runnable介面的方式建立執行緒

      public class WelcomeApp1 {
      
        public static void main(String[] args) {
          // 建立執行緒
          Thread welcomeThread = new Thread(new WelcomeTask());
      
          // 啟動執行緒
          welcomeThread.start();
          // 輸出“當前執行緒”的執行緒名稱
          System.out.printf("1.Welcome! I'm %s.%n", Thread.currentThread().getName());
      
        }
      
      }
      
      class WelcomeTask implements Runnable {
        // 在該方法中實現執行緒的任務處理邏輯
        @Override
        public void run() {
          // 輸出“當前執行緒”的執行緒名稱
          System.out.printf("2.Welcome! I'm %s.%n", Thread.currentThread().getName());
        }
      
      }
  5. 不管使用以上哪種方式執行,一旦執行緒的run方法執行結束,相應執行緒的執行也就結束了,執行結束的執行緒所佔的資源會如同其它java物件一樣被java虛擬機器垃圾回收。
  6. Thread的例項只能start一次,若多次呼叫一個例項的start()會丟擲IllegalThreadStateException異常。
  7. 可以通過Thread.currentThread()獲取當前執行緒,進而可以對其進行屬性設定或獲取它的相關資訊,例如:

    Thread.currentThread().setName("執行緒A");
    Thread.currentThread().getName();
  8. 上述中,執行緒的run方法一般由java虛擬機器呼叫,但是,執行緒也是一個Thread類的一個例項其次run方法也是由public修飾符修飾,所以run方法也能被直接呼叫,但是一般不會這麼做,違揹我們床架執行緒的初衷。

    public class WelcomeApp {
    
      public static void main(String[] args) {
        // 建立執行緒
        Thread welcomeThread = new WelcomeThread();
        // 啟動執行緒
        welcomeThread.start();
        // 輸出“當前執行緒”的執行緒名稱
        System.out.printf("1.Welcome! I'm %s.%n", Thread.currentThread().getName());
        welcomeThread.run();
      }
    }
    
    // 定義Thread類的子類
    class WelcomeThread extends Thread {
    
      // 在該方法中實現執行緒的任務處理邏輯
      @Override
      public void run() {
        System.out.printf("2.Welcome! I'm %s.%n", Thread.currentThread().getName());
      }
    }
    ==================結果==================
    1.Welcome! I'm main.
    2.Welcome! I'm Thread-0.
    2.Welcome! I'm main.
    
  9. 執行緒的屬性

    屬性屬性型別及用途只讀重要注意事項
    編號
    ID
    用於標識不同的執行緒,不同的執行緒擁有不同的編號某個編號的執行緒執行結束後,該編號可能被後續建立的執行緒使用。不同執行緒擁有的編號雖然不同,但是這種編號的唯一性只在Java虛擬機器的一次執行有效。也就是說重啟一個Java虛擬機器(如重啟Web伺服器)後,某些執行緒的編號可能與上次Java虛擬機器執行的某個執行緒的編號一樣,因此該屬性的值不適合用作某種唯一標識,特別是作為資料庫中的唯一標識(如主鍵)
    名稱
    Name
    用於區分不同的執行緒。預設值與執行緒的編號有關,預設值的格式為:Thread-執行緒編號,如Thread-0Java並不禁止我們將不同的執行緒的名稱屬性設定為相同的值。儘管如此,設定執行緒的名稱屬性有助於程式碼除錯和問題定位
    執行緒類別
    Daemon
    值為true表示相應的執行緒為守護執行緒,否則表示相應的執行緒為使用者執行緒。該屬性的預設值與相應執行緒的父執行緒的該屬性的值相同,在正常停止java程式時,當有使用者執行緒還沒執行完虛擬機器不會立即停止,會等待其執行完畢,但是如果只有守護執行緒還沒執行完則不會阻止虛擬機器停止,這說明守護執行緒通常用於執行一些重要性不是很高的任務,例如監視其它執行緒的該屬性必須在相應執行緒啟動之前設定,即對setDaemon方法的呼叫必須在對start方法的呼叫之前,否則setDaemon方法會丟擲IllegalThreadStateException異常。負責一些關鍵任務處理的執行緒不適宜設定為守護執行緒
    優先順序
    Priority
    該屬性本質上是給執行緒排程器的提示,用於表示應用程式希望哪個執行緒能夠優先得以執行。Java定義了1~10的10個優先順序。預設值一般為5。對於具體的一個執行緒而言,其優先順序的預設值與其父執行緒(建立該執行緒的執行緒)的優先順序值相等一般使用預設優先順序即可。不恰當地設定該屬性值可能導致嚴重的問題(執行緒飢餓)
  10. 執行緒方法

    方法功能備註
    static Thread
    currentThread()
    返回當前執行緒,即當前程式碼的執行執行緒(物件)同一段程式碼對Thread.currentThread()的呼叫,其返回值可能對應著不同的執行緒(物件)
    void run()用於實現執行緒的任務處理邏輯該方法是由Java虛擬機器直接呼叫的,一般情況下應用程式不應該呼叫該方法
    void start()啟動相應執行緒該方法的返回並不代表相應的執行緒已經被啟動。一個Thread例項的start方法只能夠被呼叫一次,多次呼叫會導致異常的丟擲
    void join()等待相應執行緒執行結束若執行緒A呼叫執行緒B的join方法,那麼執行緒A的執行會被暫停,直到執行緒B執行結束
    static void yield()使當前執行緒主動放棄其對處理器的佔用,這可能導致當前執行緒被暫停這個方法是不可靠的。該方法被呼叫時當前執行緒可能仍然繼續執行(視系統當前的執行狀況而定)
    static void sleep(long millis)使當前執行緒休眠(暫停執行)指定的時間

Thread和Runnable的關係

  1. Thread類實現Runnable介面

    public class Thread implements Runnable{}
    
    
  2. Runnable是一個interface且其中只有一個方法,所以實現Runnable的類要重新run方法。
  3. 兩種方式執行任務邏輯的過程

    • 實現Runnable介面,重寫run方法

      class WelcomeTask implements Runnable {
        @Override
        public void run() {
          //任務邏輯 
        }
      }
      
      new Thread(new WelcomeTask()).start();

      WelcomeTask例項在Thread中的傳遞過程,它最終會被賦值給Thread中的一個target成員變數

      private Runnable target;

      具體過程如下:
      1.有參建構函式,生成一個ThreadName,例如:Thread-0

      public Thread(Runnable target) {
          init(null, target, "Thread-" + nextThreadNum(), 0);
      }

      2.進入第一個初始化函式,這裡面無需關注其它引數,因為還有其它構造方法會呼叫init(...)

      private void init(ThreadGroup g, Runnable target, String name,long stackSize){
          init(g, target, name, stackSize, null, true);
      }

      3.第二層初始化函式,即將target賦值

      private void init(ThreadGroup g, Runnable target, String name,
                        long stackSize, AccessControlContext acc,
                        boolean inheritThreadLocals) {
          if (name == null) {
              throw new NullPointerException("name cannot be null");
          }
      
          this.name = name;
          .....
          this.target = target;
          ....
      }

      4.Thread例項呼叫start()方法

      public synchronized void start() {
          //threadStatus != 0代表這個Thread例項已經被start()過了,所以會丟擲異常
          if (threadStatus != 0)
              throw new IllegalThreadStateException();
          ...
          try {
              //最終會呼叫本地方法start0()來啟動這個執行緒
              start0();
              ...
          } finally {
              ...
          }
      }

      5.本地方法啟動執行緒,即將執行緒交給虛擬機器的執行緒排程器

      private native void start0();

      6.執行緒排程器執行執行緒任務邏輯

      //啟動執行緒後,排程器會幫助我們執行執行執行緒run()方法,即任務邏輯
      //如果target != null的時候會呼叫WelcomeTask中實現的任務邏輯,否則什麼都不會執行
      public void run() {
          if (target != null) {
              target.run();
          }
      }
      
    • 繼承Thread類,重寫run方法

      class WelcomeThread extends Thread {
           @Override
           public void run() {
             //任務邏輯
           }
      }
          
      new WelcomeThread().start();
      ...
      //任務排程器直接呼叫WelcomeThread中實現的任務邏輯
  4. 兩種方式的區別

    • 物件導向角度:一種是基於繼承實現extends Thread,一種是基於組合new Thread(new Runable()),組合相對與繼承來說耦合性更低。
    • 物件共享的角度:一個CountingTask例項可以被多個執行緒共享所以可能出現資源競爭問題。

      class CountingThread extends Thread {
          int count = 0;
          @Override
          public void run() {
              for (int i = 0; i < 100; i++) {
                  i++;
              }
          }
          System.out.println("count:" + count);
      }
      
      class CountingTask implments Runnable {
          int count = 0;
          @Override
          public void run() {
              for (int i = 0; i < 100; i++) {
                  i++;
              }
          }
          System.out.println("count:" + count);
      }
      
      public static void main(String[] args) {
          Thread thread;
          CountingTask task = new CountingTask();
          for(int i = 0;i < 10;i++){
              thread = new Thread(task);
              thread.start();
          }
          for(int i = 0;i < 10;i++){
              thread = new CountingThread();
              thread.start();
          }
      }

執行緒的生命週期

  1. New(新建立)
    Thread thread = new Thread();
    此時執行緒只是備new出來並沒有開始執行。
  2. Runnable(可執行)
    thread.start();
    1.ready:還沒有被分配資源 。
    2.running:正在執行 。
  3. 阻塞
    1.Blocked(阻塞) 沒有獲取被synchronized保護的程式碼的鎖 。
    2.Waiting(等待)

     1.Object.wait(); 
         - o.notify()/o.notifyAll()
     2.Thread.join(); 
         - join執行緒結束/被中斷 
     Blocked與Waiting的區別是Blocked是在等待釋放某個資源,Waiting是在等待達到某個條件 。
  4. Time Waiting(計時等待) 設定了時間引數的一些阻塞 。
    1.Thread.sleep(m);

      - 時間超時/join的程式結束/被中斷 。

    2.Object.wait(m)

     - 時間到/o.notify()/o.notifyAll() 

    3.Thread.join(m)

     - 時間超時/join的程式結束/被中斷 。
  5. Terminated(終止)
    1.run方法正常執行完畢 。
    2.出現沒有捕獲的異常,意外終止。

相關文章