多執行緒之執行緒池(上)
前言
最近一段時間,我們一直都在分享多執行緒相關知識,也一直有用執行緒池,但是一直沒有介紹執行緒池相關知識,所以今天我們就先來看下執行緒池相關的知識點。
執行緒池
ThreadPool
,執行緒池,顧名思義就是存放執行緒的池子,也是jdk1.5
引入的。對我們而言,它的最主要優勢就是簡化了執行緒啟動流程,讓我們可以更方便地使用多執行緒,再也不用手動start
執行緒,直接通過執行緒池提交我們的任務即可,而且合理使用執行緒池至於可以帶來以下幾個好處:
-
降低資源消耗:複用執行緒,降低建立和銷燬執行緒帶來的資源消耗
-
提高響應速度:使用執行緒池,省去了執行緒建立和初始化過程,所以任務可以更快執行
-
提高執行緒的可管理性:可以直接通過執行緒池管理、監控、排程執行緒,執行緒管理更方便
常用執行緒池
常用的執行緒池有SingleThreadExecutor
、CachedThreadPool
、ScheduledThreadPool
、FixedThreadPool
,他們分別是單執行緒排程器,快取執行緒池,定時任務執行緒池和固定執行緒池,他們都可以通過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
),我們發現執行緒池的size
是0
,活動執行緒數也是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
也就無效了。
總結
原本打算執行緒池一次分享完的,但是實際分享過程中發現內容太多了(已經一千五百字了),所以今天就先到這裡,明天再繼續分享執行緒池其他內容。總的來說,今天的內容已經說明白了執行緒池很多基礎的知識點(反正我自己覺得我都有好多收穫),算是乾貨滿滿吧,你如果掌握了這些知識點,至少在使用執行緒池的過程中會少踩好多坑。好了,今天就先說這麼多吧!