再談執行緒池——深入剖析執行緒池的前世今生

再談執行緒池——深入剖析執行緒池的前世今生

由執行緒到執行緒池

執行緒在做什麼?

靈魂拷問:寫了那麼多程式碼,你能夠用一句話簡練描述執行緒在幹啥嗎?

public class Demo01 {

  public static void main(String[] args) {
    var thread = new Thread(() -> {
      System.out.println("Hello world from a Java thread");
    });
    thread.start();
  }
}

我們上面的這個用執行緒輸出字元串的程式碼來進行說明。我們知道上面的Java程式碼啟動了一個執行緒,然後執行lambda函數,在以前沒有lambda表達式的時候我們可以使用匿名內部類實現,向下面這樣。

public class Demo01 {

  public static void main(String[] args) {
    var thread = new Thread(new Runnable() {
      @Override
      public void run() {
        System.out.println("Hello world from a Java thread");
      }
    });
    thread.start();
  }
}

但是本質上Java編譯器在編譯的時候都認為傳遞給他的是一個對象,然後執行對象的run方法。剛剛我們使用的Thread的構造函數如下:

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

Thread在拿到這個對象的時候,當我們執行Threadstart方法的時候,會執行到一個native方法start0

當JVM執行到這個方法的時候會調用作業系統給上層提供的API創建一個執行緒,然後這個執行緒會去解釋執行我們之前給Thread對象傳入的對象的run方法位元組碼,當run方法位元組碼執行完成之後,這個執行緒就會退出。

看到這裡我們仔細思考一下執行緒在做一件什麼樣的事情,JVM給我們創建一個執行緒好像執行完一個函數(run)的位元組碼之後就退出了,執行緒的生命周期就結束了。確實是這樣的,JVM給我們提供的執行緒就是去完成一個函數,然後退出(記住這一點,這一點很重要,為你後面理解執行緒池的原理有很大的幫助)。事實上JVM在使用作業系統給他提供的執行緒的時候也是給這個執行緒傳遞一個函數地址,然後讓這個執行緒執行完這個函數。只不過JVM給作業系統傳遞的函數,這個函數的功能就是去解釋執行位元組碼,當解釋執行位元組碼完成之後,這個函數也會退出(被系統回收)。

看到這裡可以將執行緒的功能總結成一句話:執行一個函數,當這個函數執行完成之後,執行緒就會退出,然後被回收,當然這個函數可以調用其他的函數,可能你會覺得這句話非常簡單,但是這句話會我們理解執行緒池的原理非常有幫助。

為什麼需要執行緒池

上面我們已經談到了,當我們執行start的方法的時候,最終會走到start0方法,這是一個native方法,JVM在執行這個方法的時候會通過系統底層函數創建一個執行緒,然後去執行run方法,這裡需要注意,創建執行緒是需要系統資源的,比如說記憶體,因為作業系統是系統資源的管理者,因此一般需要系統資源的方法都需要作業系統的參與,因此創建執行緒需要作業系統的幫忙,而一旦需要作業系統介入,執行程式碼的狀態就需要從用戶態到內核態轉換(內核態能夠執行許多用戶態不能夠執行的指令),當作業系統創建完執行緒之後有需要返回用戶態,我們的程式碼將繼續被執行,整個過程像下面這樣。

從上圖可以看到我們需要兩次的上下文切換,同時還需要執行一些作業系統的函數,這個過程是非常耗時間的,如果在並發非常高的情況,我們頻繁的去生成執行緒然後銷毀,這對我們程式的性能影響還是非常大的。因此許許多多聰明的程式設計師就想能不能不去頻繁的創建執行緒而且也能夠完成我們的功能——我們創建執行緒的目的就是想讓我們的程式完成的更加快速,讓多個不同的執行緒同時執行不同的任務。於是執行緒池就被創造出來了。執行緒池的結構大致如下所示:

執行緒池實現原理

在前面我們已經提到了關於執行緒池和執行緒比較重要的兩個點:

  • 執行緒就是執行一個函數。
  • 執行緒池當中的執行緒可以執行很多函數,但是不會退出。

憑藉你樸素的情感😂,你覺得如何實現上面兩個要求。答案就是在一個函數當中進行while循環,然後不斷的從任務隊列當中獲取任務函數,然後進行執行,直到要求停止執行緒池當中的執行緒的時候執行緒再進行退出,整個過程的程式碼大致如下所示:

  public void run() {
    while (!isStopped) {
      try {
        Runnable task = tasks.take();
        task.run();
      } catch (InterruptedException e) {
        // do nothing
      }
    }
  }

關於執行緒池要有一部分細節也很重要,比如說我們需要一個並發安全的阻塞隊列,如何保證所有執行緒正常退出等等,我們在下篇文章當中進行實現,而且將仔細分析這裡面的細節。

總結

在本篇文章當中主要給大家介紹了執行緒到執行緒池的演化過程,主要介紹執行緒池實現的基本原理,主要解讀了執行緒池背後的基本原理,希望大家有所收穫!


以上就是本篇文章的所有內容了,我是LeHung,我們下期再見!!!更多精彩內容合集可訪問項目://github.com/Chang-LeHung/CSCore

關注公眾號:一無是處的研究僧,了解更多電腦(Java、Python、電腦系統基礎、演算法與數據結構)知識。