ThreadPoolExecutor的使用和工作原理


前言

    在我們進行開發時,為了加快程序的運行效率,可能會使用到線程池去加快程序效率,但是線程池也不是隨便使用的,如果一旦使用錯誤,還可能會造成生產事故。在JDK1.5后提供了Executor框架來供開發者使用,無需關心任務如何被執行,如果不清楚線程池原理的話,使用Executor框架也可能會造成生產事故,下面主要來分析一下Executor框架和線程池ThreadPoolExecutor底層的原理

1.Executors提供的線程池

    Executors一共給我們提供了四種類型的線程池,分別是:newSingleThreadExecutor(),newFixedThreadExecutor(),newCachedThreadPool(),newScheduledThreadPool()

    (1) newSingleThreadPool()

         創建單一線程的線程池,也就是線程池中只有一個線程在執行

    (2) newFixedThreadPool()

          創建一個定長的線程池,也就是線程池中的線程數量是固定大小的

    (3) newCachedThreadPool()

           創建一個可緩存的線程池,如果線程池中的線程執行完后,沒有Task任務進來,那么當前線程可以緩存60s,如果60s過后,線程沒有被調用,則移除該線程,適用於短期異步任務的場景

  (4) newScheduledThreadPool()

             創建一個支持定時和周期性執行的線程池,基於DelayedWorkQueue延遲隊列來實現定時執行

2.四種線程池底層實現原理

    我們將代碼跟入深一點,就會發現四種線程池最終調用的還是new ThreadPoolExecutor(),只不過傳入參數不同而已。在《阿里巴巴開發手冊》中明確指出不建議我們直接Executors創建現成線程池,我們可以看到上面的四種線程池它們有一個參數也就是要傳入的阻塞隊列

    newSingleThreadPool()傳入的阻塞隊列LinkedBlockingQueue()基於鏈表實現的阻塞隊列,隊列最大長度為Integer.MAX_VALUE,也就是這個隊列可以承載2^31大小的線程,這時候服務器肯定會承受不住這么多的線程,會造成服務器崩潰的場景

    newFixedThreadPool()跟newSingleThreadPool()一樣,傳入的都是相當於無界的一個隊列

    newCachedThreadPool()和newScheduledThreadPool()可以看到傳入的一個最大線程數為Integer.MAX_VALUE,也就是會一直創建新的線程去執行任務,這時候服務器也是承受不住的,會造成服務器崩潰。

    上述原因也就是阿里開發手冊為什么不建議我們直接使用創建好的線程池使用,如果開發者稍有一不注意,就會造成服務器線程崩潰。所以我們應該使用底層的ThreadPoolExecutor()類創建線程池,這樣自己能夠清楚的知道自己設置線程池和隊列大小

3.ThreadPoolExecutor 7個參數

先看一些ThreadPoolExecutor的構造方法:

    (1) corePoolSize

        線程池中存在的核心線程數,也就是線程池中不會被釋放的線程數量,有網友可能會有疑問,如果線程不會被釋放,那不會一直被占着資源?不會,存活的線程不會跟其它線程搶占資源,相當於處於一個假死狀態,有任務過來就會喚醒存活的線程執行任務

     (2) maximumPoolSize

         線程池中允許的最大線程數,也就是任務數 > corePoolSize並且阻塞隊列也已經放滿了任務,這是最大線程數就會起作用,則創建新的線程執行任務,前提是任務數 < maximumPoolSize

    (3) keepAliveTime

          線程空閑時的存活時間,大於corePoolSize數量被創建的線程在沒有任務執行的情況下的存活時間,一旦達到存活時間線程則會被回收釋放

    (4) unit

       keepAliveTime存活時間的單位

    (5) workQueue

       當任務數 > corePoolSize核心線程數時,workQueue就會起作用,用來保存等待被執行的任務的阻塞隊列,JDK提供了多種阻塞隊列:

        1.ArrayBlockingQueue:基於數組結構的有界阻塞隊列,按FIFO排序任務

        2.LinkedBlockingQuene:基於鏈表結構的阻塞隊列,按FIFO排序任務

        3.SynchronousQuene:一個不存儲元素的阻塞隊列,每個插入操作必須等到另一個線程調用移除操作,否則插入操作一直處於阻塞狀態

        4.priorityBlockingQuene:具有優先級的無界阻塞隊列

        5.DelayQueue: 支持延時獲取元素的無界阻塞隊列,在創建元素時可以指定多久才能從隊列中獲取當前元素

        6.LinkedTransferQueue: 由一個鏈表結構組成的無界阻塞TransferQueue隊列

        7.LinkedBlockingDeque: 由一個鏈表結構組成的雙向阻塞隊列,可以從隊列的兩端插入和移出元素

    (6) threadFactory

       創建線程的工廠,默認實現DefaultThreadFactory

    (7) handler

       線程池的淘汰策略,當過來的任務數 > corePoolSize, 阻塞隊列已滿,> maximumPoolSize, 這時handler就會起作用,會采用一種策略來處理過來的任務數,JDK也提供了4中淘汰策略:

        1. AbortPolicy:直接拋出異常,默認策略

        2. CallerRunsPolicy:用調用者所在的線程來執行任務

        3. DiscardOldestPolicy: 丟棄隊列中最近的一個任務,並執行當前任務

        4. DiscardPolicy: 不處理,直接丟棄掉

    當然我們也可以自定義自己的淘汰策略,需要實現接口RejectedExecutionHandler,重寫rejectedExecution方法,可以自定義加一些日志或者持久化不能處理的任務

4.ThreadPoolExecutor工作流程

    當一個新的任務提交給線程池時,線程池的處理步驟:

        (1) 首先判斷核心線程數是否已滿,如果沒滿,則調用一個線程處理Task任務,如果已滿,則執行步驟(2)

        (2) 這時會判斷阻塞隊列是否已滿,如果阻塞隊列沒滿,就將Task任務加入到阻塞隊列中等待執行,如果阻塞隊列已滿,則執行步驟(3)

        (3) 判斷是否大於最大線程數,如果小於最大線程數,則創建線程執行Task任務,如果大於最大線程數,則執行步驟(4)

        (4) 這時會使用淘汰策略來處理無法執行的Task任務

大概的工作流程:

為了更好的理解,可以將線程池比喻成一個工廠,核心線程數就好比是工廠里面正式員工,工廠可能接受到了很多訂單要進行生產,這時人手不夠,如果加人手,這時從外面招了幾個臨時員工,但是也不可能一直招,會有個最大的限制,這時就是最大線程數了,這部分臨時員工就屬於 > 阻塞隊列,< 最大線程數中的線程。阻塞隊列就好比工廠里面的倉庫,正式員工不夠,后面過來的訂單生產就會被放到倉庫中。

5.線程的狀態詳解

    線程的狀態相信大家可能都知道,但有時容易忘記,記不住各種狀態的轉換,在JDK中Thread類中提供了一個枚舉類,這個枚舉類就例舉了線程的各個狀態

我們可以看到一共定義了6個枚舉值,其實代表的是5種類型的線程狀態, 5種線程狀態:

        1.NEW(新建)

        2.RUNNABLE(運行狀態)

        3.BLOCKED(阻塞狀態)

        4.WAITING(等待狀態,WAITING和TIMED_WAITING可以歸為一類,都屬於等待狀態,只是后者可以設置等待時間,即等待多久)

        5.TERMINATED(終止狀態)

線程關系轉換圖:

當new Thread()說明這個線程處於NEW(新建狀態),調用Thread.start()方法表示這個線程處於RUNNABLE(運行狀態),但是RUNNABLE狀態中又包含了兩種狀態,一個是READY(就緒狀態), RUNNING(運行中),我們都知道調用start()線程不一定獲得了CPU時間片,這時候就處於READY,等待CPU時間片,當獲得了CPU時間片,這時就會處於RUNNING狀態,可能在運行中調用synchronized同步的代碼塊,沒有獲取到鎖,這時會處於BLOCKED(阻塞狀態),當重新獲取到鎖時,又會變為RUNNING狀態,中間在代碼執行的過程中可能會碰到Object.wait()等一些等待方法,線程的狀態又會轉變為WAITING(等待狀態),等待被喚醒,當調用了Object.notifyAll()喚醒了之后線程執行完就會變為TERMINATED(終止狀態),直至線程的狀態轉換關系解釋到這里

6.線程池的5種狀態

    線程池中狀態通過2個二進制位(bit)表示,用來表示線程池定義的5個狀態:RUNNINGSHUTDOWNSTOPTIDYINGTERMINATED

        RUNNING:線程池正常工作的狀態,在 RUNNING 狀態下線程池接受新的任務並處理任務隊列中的任務

        SHUTDOWN:調用shutdown()方法會進入 SHUTDOWN 狀態。在 SHUTDOWN 狀態下,線程池不接受新的任務,但是會繼續執行任務隊列中已有的任務。

        STOP:調用shutdownNow()會進入 STOP 狀態。在 STOP 狀態下線程池既不接受新的任務,也不處理已經在隊列中的任務。對於還在執行任務的工作線程,線程池會發起中斷請求來中斷正在執行的任務,同時會清空任務隊列中還未被執行的任務

        TIDYING:當線程池中的所有執行任務的工作線程都已經終止,並且工作線程集合為空的時候,進入 TIDYING 狀態

        TERMINATED:當線程池執行完terminated()鈎子方法以后,線程池進入終態 TERMINATED

 

總結:

    以上是對ThreadPoolExecutor()線程池的一個總結,包括每個參數什么意思,線程池的工作流程,線程的狀態轉換,還有線程池的狀態,寫的還是比較基礎的,沒有對照着源碼具體去分析線程池的工作狀態,有時間的話將ThreadPoolExecutor()工作狀態寫的更深一點


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM