阿里大佬帶你,深刻理解線程池底層原理

爲何要使用線程池

在實際使用中,線程是很佔用系統資源的,若是對線程管理不善很容易致使系統問題。 所以,在大多數併發框架中都會使用線程池來管理線程,使用線程池管理線程主要有以下好處:數據庫

(1)下降資源消耗。經過複用已存在的線程和下降線程關閉的次數來儘量下降系統性能損耗bash

(2)提高系統響應速度。經過複用線程,省去建立線程的過程,所以總體上提高了系統的響應速度數據結構

(3)提升線程的可管理性。線程是稀缺資源,若是無限制的建立,不只會消耗系統資源, 還會下降系統的穩定性,所以,須要使用線程池來管理線程。併發

線程池的工做原理

當一個併發任務提交給線程池,線程池分配線程去執行任務的過程以下:框架



線程池執行所提交的任務過程主要有這樣幾個階段:異步

(1)先判斷線程池中核心線程池全部的線程是否都在執行任務。 若是不是,則新建立一個線程執行剛提交的任務,不然,核心線程池中全部的線程都在執行任務,則進入(2)性能

(2)判斷當前阻塞隊列是否已滿,若是未滿, 則將提交的任務放置在阻塞隊列中;不然,則進入(3)ui

(3)判斷線程池中全部的線程是否都在執行任務, 若是沒有,則建立一個新的線程來執行任務,不然,則交給飽和策略進行處理spa

線程池執行邏輯

經過ThreadPoolExecutor建立線程池後,提交任務後執行過程是怎樣的,下面來經過源碼來看一看。execute()方法源碼以下:線程

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    int c = ctl.get();
	//若是線程池的線程個數少於corePoolSize則建立新線程執行當前任務
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
	//若是線程個數大於corePoolSize或者建立線程失敗,則將任務存放在阻塞隊列workQueue中
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        if (! isRunning(recheck) && remove(command))
            reject(command);
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
	//若是當前任務沒法放進阻塞隊列中,則建立新的線程來執行任務
    else if (!addWorker(command, false))
        reject(command);
}複製代碼

execute()執行過程:

execute方法執行邏輯有這樣幾種狀況:

(1)若是當前運行的線程少於corePoolSize,則會建立新的線程來執行新的任務,即便線程池中的其餘線程是空閒的;

(2)若是運行的線程個數等於或者大於corePoolSize且小於maximumPoolSize,則會將提交的任務存放到阻塞隊列workQueue中;

(3)若是當前workQueue隊列已滿的話,則會建立新的線程來執行任務;

(4)若是線程個數已經超過了maximumPoolSize,則會使用飽和策略RejectedExecutionHandler來進行處理增量的任務。



線程池的關閉

關閉線程池,能夠經過shutdown和shutdownNow這兩個方法。 它們的原理都是遍歷線程池中全部的線程,而後依次中斷線程。 shutdown和shutdownNow仍是有不同的地方:

  • shutdownNow首先將線程池的狀態設置爲STOP,而後嘗試中止全部的正在執行和未執行任務的線程,並返回等待執行任務的列表
  • shutdown只是將線程池的狀態設置爲SHUTDOWN狀態,而後中斷全部沒有正在執行任務的線程

能夠看出shutdown方法會將正在執行的任務繼續執行完,而shutdownNow會直接中斷正在執行的任務。 調用了這兩個方法的任意一個,isShutdown方法都會返回true, 當全部的線程都關閉成功,才表示線程池成功關閉,這時調用isTerminated方法纔會返回true。

如何合理配置線程池參數

要想合理的配置線程池,就必須首先分析任務特性,能夠從如下幾個角度來進行分析:

  • 任務的性質:CPU密集型任務,IO密集型任務和混合型任務。
  • 任務的優先級:高,中和低。
  • 任務的執行時間:長,中和短。
  • 任務的依賴性:是否依賴其餘系統資源,如數據庫鏈接。
  1. CPU密集型任務配置儘量少的線程數量,如配置(N cpu)+1個線程的線程池。
  2. IO密集型任務則因爲須要等待IO操做,線程並非一直在執行任務,則配置儘量多的線程,如2 * (N cpu)。
  3. 混合型的任務,若是能夠拆分,則將其拆分紅一個CPU密集型任務和一個IO密集型任務,只要這兩個任務執行的時間相差不是太大,那麼分解後執行的吞吐率要高於串行執行的吞吐率,若是這兩個任務執行時間相差太大,則不必進行分解。
  4. 咱們能夠經過Runtime.getRuntime().availableProcessors()方法得到當前設備的CPU個數。

優先級不一樣的任務可使用優先級隊列PriorityBlockingQueue來處理。它可讓優先級高的任務先獲得執行,須要注意的是若是一直有優先級高的任務提交到隊列裏,那麼優先級低的任務可能永遠不能執行。

執行時間不一樣的任務能夠交給不一樣規模的線程池來處理,或者也可使用優先級隊列,讓執行時間短的任務先執行。

依賴數據庫鏈接池的任務,由於線程提交SQL後須要等待數據庫返回結果,若是等待的時間越長CPU空閒時間就越長,那麼線程數應該設置越大,這樣才能更好的利用CPU。

阻塞隊列最好是使用有界隊列,若是採用無界隊列的話,一旦任務積壓在阻塞隊列中的話就會佔用過多的內存資源,甚至會使得系統崩潰。

ScheduledThreadPoolExecutor

ScheduledThreadPoolExecutor繼承了ThreadPoolExecutor類, 所以,總體上功能一致,線程池主要負責建立線程(Worker類), 線程從阻塞隊列中不斷獲取新的異步任務,直到阻塞隊列中已經沒有了異步任務爲止。 可是相較於ThreadPoolExecutor來講,ScheduledThreadPoolExecutor 具備延時執行任務週期性執行任務的特性, ScheduledThreadPoolExecutor從新設計了任務類ScheduleFutureTask, ScheduleFutureTask重寫了run方法使其具備可延時執行和可週期性執行任務的特性。 另外,阻塞隊列DelayedWorkQueue是可根據優先級排序的隊列,採用了堆的底層數據結構, 使得與當前時間相比,將待執行時間越靠近的任務放置到隊頭,以便線程可以獲取到任務進行執行

線程池不管是ThreadPoolExecutor仍是ScheduledThreadPoolExecutor, 在設計時的三個關鍵要素是:任務執行者以及任務結果。 它們的設計思想也是徹底將這三個關鍵要素進行了解耦。

執行者

任務的執行機制,徹底交由Worker類,也就是進一步了封裝了Thread。 向線程池提交任務,不管爲ThreadPoolExecutor的execute方法和submit方法, 仍是ScheduledThreadPoolExecutor的schedule方法,都是先將任務移入到阻塞隊列中, 而後經過addWork方法新建了Work類,並經過runWorker方法啓動線程,並 不斷的從阻塞對列中獲取異步任務執行交給Worker執行,直至阻塞隊列中沒法取到任務爲止。

任務

在ThreadPoolExecutor和ScheduledThreadPoolExecutor中任務是指實現了Runnable接口和Callable接口的實現類。 ThreadPoolExecutor中會將任務轉換成FutureTask類, 而在ScheduledThreadPoolExecutor中爲了實現可延時執行任務和週期性執行任務的特性, 任務會被轉換成ScheduledFutureTask類,該類繼承了FutureTask,並重寫了run方法。

任務結果

在ThreadPoolExecutor中提交任務後,獲取任務結果能夠經過Future接口的類, 在ThreadPoolExecutor中實際上爲FutureTask類, 而在ScheduledThreadPoolExecutor中則是ScheduledFutureTask類

線程池的狀態

線程池的狀態有:

  • RUNNING:能接受新提交的任務,而且也可以處理阻塞隊列中的任務;
  • SHUTDOWN:再也不接受新提交的任務,可是能夠處理存量任務(即阻塞隊列中的任務);
  • STOP:再也不接受新提交的任務,也不處理存量任務;
  • TIDYING:全部任務都已終止;
  • TERMINATED:默認是什麼也不作的,只是做爲一個標識。

狀態轉移以下圖所示:



工做線程的生命週期:


相關文章
相關標籤/搜索