Java執行緒池一:執行緒基礎

油多壞不了菜發表於2020-11-29

最近精讀Netty原始碼,讀到NioEventLoop部分的時候,發現對Java執行緒&執行緒池有些概念還有困惑, 所以深入總結一下

執行緒建立

Java執行緒建立主要有三種方式:繼承Thread類、實現Runable介面、實現Callable介面

只有通過呼叫Thread.start() 方法才會真正建立一個執行緒, 呼叫Thread.run() 並不會

當呼叫執行緒關心任務執行結果時,我們應選擇實現Callable介面的方式建立執行緒

  • 繼承方式實現建立執行緒

    @Test
    public void testCreate_1() {
      Thread t = new Thread() {
        @Override
        public void run() {
          System.out.println(Thread.currentThread().getName());
          throw new RuntimeException();
        }
      };
      t.start();
      t.run();
    }
    
  • 實現Runnable介面的方式建立執行緒,這種方式呼叫執行緒無法感知任務執行緒執行結果(是否執行、成功或者異常)

    @Test
    public void testCreate_2() {
      Thread t = new Thread(() -> System.out.println(Thread.currentThread().getName()));
      t.start();
    }
    
  • 實現Callable介面,呼叫執行緒通過FutureTask物件獲取執行結果(返回值或者異常)

    @Test
    public void testCreate_3() throws ExecutionException, InterruptedException {
      FutureTask<Integer> task = new FutureTask<>(() -> {
        throw new RuntimeException();
      });
      new Thread(task).start();
      System.out.println(task.get());
    }
    

執行緒的狀態

我們知道Java執行緒是使用系統核心執行緒實現, 所以先來簡單回顧下系統核心執行緒的狀態

核心執行緒的狀態

  • Ready狀態:當前執行緒已經就緒,等待系統排程
  • Running狀態:當Ready狀態的執行緒分配到時間片後進入該狀態
  • Blocking狀態:執行中的執行緒因為其他資源未就緒進入該狀態

核心執行緒的狀態

Java執行緒的狀態

  • NEW: 例項化一個Thread物件後未呼叫start方法前都是該狀態
  • RUNNABLE: JVM裡面的可執行狀態,對應核心執行緒的Ready或者Running狀態。所以該狀態下執行緒不一定在在執行,有可能在等待排程
  • WAITING: 等待狀態,需要其他執行緒喚醒後才能重新進入RUNNABLE狀態
  • TIMED_WAITING:超時等待,等待一定的時間或者被其他執行緒喚醒之後可再進入RUNNABLE狀態
  • BLOCKED:阻塞狀態,特指等待進入synchronized同步塊的狀態(獲取監視器鎖)
  • Termination:終態

只有待獲取監視器鎖時才是阻塞狀態,獲取Java語言實現的鎖(ReentrantLock等)是等待狀態。二者的區別在於監視器鎖的實現依賴核心變數。

Java執行緒狀態

異常處理

假設一段業務邏輯沒有考慮執行時異常, 而執行時異常又剛好發生了,那麼對應的執行緒就會直接崩潰。所以多執行緒環境下為了讓程式更加健壯穩定, 我們需要捕獲異常。

  • 將整個業務邏輯加上異常捕獲(當然程式碼就不是很優雅)

    @Test
    public void testExceptionHandle_1() {
      new Thread(() -> {
        try {
          //business code
          int a = 1, b = 0;
          a = a / b;
        } catch (Throwable th) {
          //log
        }
      }).start();
    }
    
  • 使用FutureTask非同步回掉處理異常(更加優雅,業務邏輯和異常處理邏輯分離)

    @Test
    public void testExceptionHandle_2() {
      //業務邏輯
      FutureTask<Integer> ft = new FutureTask<>(() -> {
      //business code
      int a = 1, b = 0;
      a = a / b;
      return a;
      });
    
      Thread t = new Thread(() -> {
      ft.run();
      handleResult(ft);
      });
      t.start();
    }
    //異常處理邏輯
    private void handleResult(FutureTask<Integer> ft) {
        try {
        System.out.println("the result is " + ft.get());
        } catch (InterruptedException e) {
        //log or ...
        e.printStackTrace();
        } catch (ExecutionException e) {
        //log or ...
        e.printStackTrace();
        }
    }
    

中斷

Java中斷是一種執行緒間通訊手段。比如A執行緒給B執行緒傳送一箇中斷訊號,B執行緒收到這個訊號,可以處理也可以不處理。

  • 中斷相關的API
void thread.interrupt();//例項方法-中斷執行緒(執行緒的中斷標識位置為1)
boolean thread.isInterrupted();//執行緒是否中斷 & 不清除中斷標識
static boolean Thread.interrupted();//當前執行緒是否中斷 & 清除中斷標識
  • 例項

    執行緒t_1每次迴圈會判斷當前執行緒的中斷狀態,如果當前執行緒已經被中斷(中斷標識位為1)就直接返回;

    整個通訊過程:主執行緒把t_1執行緒的中斷標識位置為1,t_1獲取到中斷標識位為1, 然後結束迴圈。

    @Test
    public void testInterrupt() throws InterruptedException {
      Thread t_1 = new Thread(() -> {
        int i = 0;
        while (true) {
          boolean isInterrupt = Thread.interrupted();
          if (isInterrupt) {
            System.out.println("i am interrupt, return");
            return;
          }
          //business code
          if (i++ % 10000 == 0) {
            System.out.println(i);
          }
        }
      });
      t_1.start();
      for (int i = 0; i < 100000; i++) {
        ;
      }
      t_1.interrupt();
    }
    

其他

  • 守護執行緒

    當JVM中的所有的使用者執行緒都退出後守護執行緒也會退出

  • 優先順序

    執行緒的優先順序越高,越有可能更快的執行或者獲得更多的可執行時間單元。但是Java執行緒的優先順序只是參考,依賴於具體的實現

  • thread.join()

    呼叫執行緒進入WAITING狀態,直到thread執行緒終止

  • thread.yield()

    當前執行緒讓出cpu資源,仍處於RUNNABLE狀態

相關文章