執行緒池略略觀

流年的夏天發表於2020-08-06

【為什麼要使用執行緒池】

------傳統執行緒建立方式的問題

  1. 反覆建立執行緒系統開銷比較大,每個執行緒建立和銷燬都需要時間,如果任務比較簡單,那麼就有可能導致建立和銷燬執行緒消耗的資源比執行緒執行任務本身消耗的資源還要大。
  2. 過多的執行緒會佔用過多的記憶體等資源,還會帶來過多的上下文切換,同時還會導致系統不穩定。

------執行緒池的優點

  1. 執行緒池可以解決執行緒生命週期的系統開銷問題,同時因為執行緒複用,消除了建立執行緒的過程,可以加快響應速度
  2. 執行緒池會根據配置和任務數量靈活地控制執行緒數量,可以統籌記憶體和 CPU 的使用,避免資源使用不當。
  3. 執行緒池可以統一管理資源

   

【執行緒池引數】

corePoolSize

核心執行緒數

maxPoolSize

最大執行緒數

keepAliveTime+時間單位

空閒執行緒的存活時間

TheadFactory

執行緒工廠,用於建立新執行緒

workQueue

執行緒佇列(一般未阻塞佇列)

Handler

處理被拒絕的任務

   

------Spring配置:

<bean id="threadPool" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">

        <!-- 核心執行緒數  -->

        <property name="corePoolSize" value="20" />

        <!-- 最大執行緒數 -->

        <property name="maxPoolSize" value="80" />

        <!-- 佇列最大長度 >=mainExecutor.maxSize -->

        <property name="queueCapacity" value="5000" />

        <!-- 執行緒池維護執行緒所允許的空閒時間 -->

        <property name="keepAliveSeconds" value="300" />

        <!-- 執行緒池對拒絕任務(無執行緒可用)的處理策略 -->

        <property name="rejectedExecutionHandler">

            <bean class="java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy" />

        </property>

    </bean>

   

------執行緒池基本工作流:

  1. 應用初始化後,當前執行緒池執行緒數為0
  2. 有任務被提交後,建立核心執行緒執行任務;
  3. 當核心執行緒數達到上限後,任務新增到執行緒佇列;
  4. 當執行緒佇列內任務數達到上限後,建立非核心執行緒執行任務;
  5. 當執行緒池內匯流排程數達到最大執行緒數後,任務會被拒絕;
  6. 當任務漸漸被執行完,佇列為空,而且當前執行緒數大於核心執行緒數之後,執行緒池會檢測執行緒的keepAliveSeconds,如果執行緒空閒時間大於這個時間,執行緒將會被銷燬。
  7. 執行緒漸漸地被銷燬,執行緒池記憶體活執行緒等於核心執行緒數之後,則不繼續做處理,執行緒空轉,等待新的任務到來。

   

------ThreadFactory   

執行緒工廠,主要作用是生產執行緒。

我們可以選擇使用預設的執行緒工廠,建立的執行緒都會在同一個執行緒組,並擁有一樣的優先順序。

也可以選擇自己定製執行緒工廠,以方便給執行緒自定義命名。

   

【拒絕策略】

AbortPolicy

拒絕任務時,會直接丟擲一個型別為 RejectedExecutionException RuntimeException,可以感知到任務被拒絕了

DiscardPolicy

當新任務被提交後直接被丟棄掉,有一定風險,可能會造成資料丟失

DiscardOldestPolicy

丟棄任務佇列中的頭結點,通常是存活時間最長的任務,同樣有風險

CallerRunsPolicy

把這個任務交於提交任務的執行緒執行,也就是誰提交任務,誰就負責執行任務
1.
新提交的任務不會被丟棄,這樣也就不會造成業務損失
2.
由於誰提交任務誰就要負責執行任務,這樣提交任務的執行緒就得負責執行任務,而執行任務又是比較耗時的,在這段期間,提交任務的執行緒被佔用,也就不會再提交新的任務,減緩了任務提交的速度,相當於是一個負反饋。

   

【六種常見的執行緒池】

------FixedThreadPool

核心執行緒數和最大執行緒數一樣,所以是固定執行緒數的執行緒池。

   

------CachedThreadPool

可快取執行緒池,核心執行緒數為0,最大執行緒數為int最大值

佇列的容量為0,實際不儲存任何任務,它只負責對任務進行中轉和傳遞,所以效率比較高。

   

------ScheduledThreadPool

支援定時或週期性執行任務。

service.schedule(new Task(), 10, TimeUnit.SECONDS);

表示延遲指定時間後執行一次任務

service.scheduleAtFixedRate(new Task(), 10, 10, TimeUnit.SECONDS);

以固定的頻率執行任務,它的第二個引數 initialDelay 表示第一次延時時間,第三個引數 period 表示週期,也就是第一次延時後每次延時多長時間執行一次任務。

service.scheduleWithFixedDelay(new Task(), 10, 10, TimeUnit.SECONDS);

之前的 scheduleAtFixedRate 是以任務開始的時間為時間起點開始計時,時間到就開始執行第二次任務,而不管任務需要花多久執行;
scheduleWithFixedDelay 方法以任務結束的時間為下一次迴圈的時間起點開始計時。

   

------SingleThreadExecutor

使用唯一的執行緒去執行任務,可保證執行順序

   

------SingleThreadScheduledExecutor

和第三個類似,只不過執行緒只有一個

   

------ForkJoinPool

實現任務的分裂和彙總,充分利用多核CPU的計算能力。

   

【阻塞佇列】

   

------LinkedBlockingQueue     

無界佇列FixedThreadPool SingleThreadExector使用。

   

------SynchronousQueue      

SynchronousQueue是一個沒有資料緩衝BlockingQueue,生產者執行緒對其的插入操作put必須等待消費者的移除操作take,反過來也一樣。

生產者和消費者互相等待對方,握手,然後一起離開。

對應執行緒池 CachedThreadPool

   

------DelayedWorkQueue

對應執行緒池ScheduledThreadPool SingleThreadScheduledExecutor

DelayedWorkQueue 的特點是內部元素並不是按照放入的時間排序,而是會按照延遲的時間長短對任務進行排序,內部採用的是"堆"的資料結構。

   

【關閉執行緒池】

------shutdown()

安全地關閉一個執行緒池。

並不是立刻就被關閉,呼叫 shutdown() 方法後執行緒池會在執行完正在執行的任務和佇列中等待的任務後才徹底關閉。

   

------isShutdown()

可以返回 true 或者 false 來判斷執行緒池是否已經開始了關閉工作。

   

------isTerminated()

可以檢測執行緒池是否真正"終結"了,這不僅代表執行緒池已關閉,同時代表執行緒池中的所有任務都已經都執行完畢了。

   

------awaitTermination()

判斷執行緒池狀態

呼叫 awaitTermination 方法後當前執行緒會嘗試等待一段指定的時間,如果在等待時間內,執行緒池已關閉並且內部的任務都執行完畢了,也就是說執行緒池真正"終結"了,那麼方法就返回 true,否則超時返回 fasle

   

------shutdownNow()

會給所有執行緒池中的執行緒傳送 interrupt 中斷訊號,嘗試中斷這些任務的執行;

然後會將任務佇列中正在等待的所有任務轉移到一個 List 中並返回,我們可以根據返回的任務 List 來進行一些補救的操作,例如記錄在案並在後期重試。

   

【執行緒複用原理】

線上程池中,同一個執行緒從BlockingQueue裡面不斷地提取任務;

核心內容在於對Tread進行了封裝,每個執行緒都會去執行一個迴圈任務;

這個迴圈任務會不停地檢查佇列裡面是否有待執行的任務,如果有,則執行其run方法,這樣,執行緒就被串聯了起來。

相關文章