【爲什麼要使用線程池】
------傳統線程創建方式的問題
- 反覆創建線程系統開銷比較大,每個線程創建和銷燬都需要時間,如果任務比較簡單,那麼就有可能導致創建和銷燬線程消耗的資源比線程執行任務本身消耗的資源還要大。
- 過多的線程會佔用過多的內存等資源,還會帶來過多的上下文切換,同時還會導致系統不穩定。
------線程池的優點
- 線程池可以解決線程生命週期的系統開銷問題,同時因爲線程複用,消除了創建線程的過程,可以加快響應速度。
- 線程池會根據配置和任務數量靈活地控制線程數量,可以統籌內存和 CPU 的使用,避免資源使用不當。
- 線程池可以統一管理資源。
【線程池參數】
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> |
------線程池基本工作流:
- 應用初始化後,當前線程池線程數爲0;
- 有任務被提交後,創建核心線程執行任務;
- 當核心線程數達到上限後,任務添加到線程隊列;
- 當線程隊列內任務數達到上限後,創建非核心線程執行任務;
- 當線程池內總線程數達到最大線程數後,任務會被拒絕;
- 當任務漸漸被執行完,隊列爲空,而且當前線程數大於核心線程數之後,線程池會檢測線程的keepAliveSeconds,如果線程空閒時間大於這個時間,線程將會被銷燬。
- 線程漸漸地被銷燬,線程池內存活線程等於核心線程數之後,則不繼續做處理,線程空轉,等待新的任務到來。
------ThreadFactory
線程工廠,主要作用是生產線程。
我們可以選擇使用默認的線程工廠,創建的線程都會在同一個線程組,並擁有一樣的優先級。
也可以選擇自己定製線程工廠,以方便給線程自定義命名。
【拒絕策略】
AbortPolicy |
拒絕任務時,會直接拋出一個類型爲 RejectedExecutionException 的 RuntimeException,可以感知到任務被拒絕了 |
DiscardPolicy |
當新任務被提交後直接被丟棄掉,有一定風險,可能會造成數據丟失 |
DiscardOldestPolicy |
丟棄任務隊列中的頭結點,通常是存活時間最長的任務,同樣有風險 |
CallerRunsPolicy |
把這個任務交於提交任務的線程執行,也就是誰提交任務,誰就負責執行任務 |
【六種常見的線程池】
------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 是以任務開始的時間爲時間起點開始計時,時間到就開始執行第二次任務,而不管任務需要花多久執行; |
------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方法,這樣,線程就被串聯了起來。