1. 程式人生 > 其它 >多執行緒之執行緒池(上)

多執行緒之執行緒池(上)

前言

最近一段時間,我們一直都在分享多執行緒相關知識,也一直有用執行緒池,但是一直沒有介紹執行緒池相關知識,所以今天我們就先來看下執行緒池相關的知識點。

執行緒池

ThreadPool,執行緒池,顧名思義就是存放執行緒的池子,也是jdk1.5引入的。對我們而言,它的最主要優勢就是簡化了執行緒啟動流程,讓我們可以更方便地使用多執行緒,再也不用手動start執行緒,直接通過執行緒池提交我們的任務即可,而且合理使用執行緒池至於可以帶來以下幾個好處:

  • 降低資源消耗:複用執行緒,降低建立和銷燬執行緒帶來的資源消耗

  • 提高響應速度:使用執行緒池,省去了執行緒建立和初始化過程,所以任務可以更快執行

  • 提高執行緒的可管理性:可以直接通過執行緒池管理、監控、排程執行緒,執行緒管理更方便

常用執行緒池

常用的執行緒池有SingleThreadExecutorCachedThreadPoolScheduledThreadPoolFixedThreadPool,他們分別是單執行緒排程器,快取執行緒池,定時任務執行緒池和固定執行緒池,他們都可以通過Executors建立,呼叫對應的靜態方法即可,由於這一塊的內容比較多,所以今天就簡單提一下,後面專門講一次。

自定義執行緒池

我們今天著重講下自定義執行緒池,自定義執行緒池也很簡單,直接new ThreadPoolExecutor() ,然後傳入對應的引數即可,大家可以看下下面的示例:

int corePoolSize = 10;
int maximumPoolSize = 20;
long keepAliveTime = 1000;
TimeUnit unit = TimeUnit.MICROSECONDS;
BlockingDeque<Runnable> workQueue = new LinkedBlockingDeque<>();
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
for (int i = 0; i < 50; i++) {
    threadPoolExecutor.execute(() -> {
        String name = Thread.currentThread().getName();
        System.out.println("hello threadPool: "+ name);
    });
}
threadPoolExecutor.shutdown();

ThreadPoolExecutor有三個構造方法,至少需要三個引數

其中第一個引數是執行緒池的基本大小,當你提交一個任務到執行緒池時,執行緒池會建立一個執行緒來執行任務,即使又空閒的執行緒存在,執行緒池依然會啟動一個新執行緒來執行當前任務,直到執行緒池中的執行緒數達到執行緒基本大小(corePoolSize);

第二個引數是執行緒池允許建立的最大執行緒數。如果工作佇列(第三個引數)滿了,且建立執行緒數已達到執行緒池基本大小,則執行緒池會繼續建立新的執行緒來執行任務。如果你指定的工作佇列是無界的,那這個引數也就失效了。

第三個引數就是執行緒池工作佇列,就是當你需要執行的任務超過執行緒池基本大小的時候,會把超出部分放進工作佇列,等待執行緒池基本執行緒資源釋放。

下面我們分別驗證以上三點,執行上面的示例程式碼:

在第一次迴圈的時候(i=0),我們發現執行緒池的size0,活動執行緒數也是0,任務佇列也是0,完成任務數也是0,這也說明執行緒池在最開始的時候是沒有建立執行緒的;

然後我們讓他迴圈到第9次(i=8),這時候執行緒池已經被初始化,有8個執行緒(由於斷點的原因,第9個執行緒尚未被建立),活動執行緒數5,完成執行的任務數3,任務佇列還是0,說明確實在未達到執行緒池基本大小時,會不斷建立新的執行緒;

我們繼續執行,讓他迴圈到第15次(i=14),可結果似乎和我們預期不一樣,按照預期,執行緒池的size應該是10,活動執行緒數也是10,任務佇列也是4,完成任務數可能不確定,所以這裡肯定不能通過debug的方式來看了,因為debug停頓之後好多執行緒資源已經被釋放,任務根本就不會堆積,所以任務佇列就不會有資料:

所以這裡我線上程池啟動任務前加一行列印,列印執行緒池資料

然後在執行,就可以拿到執行資料:

這樣的資料才是真實的,因為實際執行的時候,執行緒啟動是非常快的,所以執行完成的任務數應該是0,等待任務數是4

我們前面設定的最大執行緒數是20,但是翻看執行記錄,我發現執行緒池的大小始終是10,說明只要不打到任務佇列的上限,並不會建立新的執行緒,這裡我們把迴圈次數改為60,然後執行下:

但是依然沒有建立新的執行緒,因為還是內沒有達到任務佇列上限,我們把迴圈次數再調大一點,調到70:

現線上程池的大小就變成了19,活動執行緒數19,但是這時候如果你繼續調大迴圈次數,執行緒池就會報錯了:

這個錯誤的原因就是執行緒池資源已經耗盡了,無法再接收新的任務了,這也就是說執行緒池能夠處理的最大任務數是corePoolSize + maximumPoolSize + workQueue.size() ,當然,如果你的workQueue不設定大小,那永遠都不會報這個錯誤,當然maximumPoolSize 也就無效了。

總結

原本打算執行緒池一次分享完的,但是實際分享過程中發現內容太多了(已經一千五百字了),所以今天就先到這裡,明天再繼續分享執行緒池其他內容。總的來說,今天的內容已經說明白了執行緒池很多基礎的知識點(反正我自己覺得我都有好多收穫),算是乾貨滿滿吧,你如果掌握了這些知識點,至少在使用執行緒池的過程中會少踩好多坑。好了,今天就先說這麼多吧!