線程池ThreadPoolTaskExecutor使用詳解
ThreadPoolTaskExecutor通常通過XML方式配置,或者通過Executors
的工廠方法進行配置。
XML方式配置代碼如下:
<bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"> <property name="corePoolSize" value="8"/> <!--核心線程數 --> <property name="maxPoolSize" value="16"/> <!--最大線程數 --> <property name="keepAliveSeconds" value ="3000"/> <!--線程最大空閑時間 --> <property name="queueCapacity" value="200"/> <!-- 隊列大小 --> <property name="threadNamePrefix" value="TASK_EXECUTOR"/> <property name="rejectedExecutionHandler"> <bean class="java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy"/> </property> </bean>
rejectedExecutionHandler字段用於配置拒絕策略,常用的拒絕策略如下:
AbortPolicy,用於被拒絕任務的處理程序,它將拋出RejectedExecutionException。
CallerRunsPolicy,用於被拒絕任務的處理程序,它直接在execute方法的調用線程中運行被拒絕的任務。
DiscardOldestPolicy,用於被拒絕任務的處理程序,它放棄最舊的未處理請求,然后重試execute。
DiscardPolicy,用於被拒絕任務的處理程序,默認情況下它將丟棄被拒絕的任務。
其他說明:
為了實現某些特殊的業務需求,用戶可以選擇使用自定義策略,只需實現RejectedExecutionHandler接口即可。
建議配置threadNamePrefix屬性,出問題時可以更方便的進行排查。
提交任務
無返回值的任務使用execute(Runnable)
有返回值的任務使用submit(Runnable)
處理流程
當一個任務被提交到線程池時,首先查看線程池的核心線程是否都在執行任務,否就選擇一條線程執行任務,是就執行第二步。
查看核心線程池是否已滿,不滿就創建一條線程執行任務,否則執行第三步。
查看任務隊列是否已滿,不滿就將任務存儲在任務隊列中,否則執行第四步。
查看線程池是否已滿,不滿就創建一條線程執行任務,否則就按照策略處理無法執行的任務。
在ThreadPoolExecutor中表現為:
如果當前運行的線程數小於corePoolSize,那么就創建線程來執行任務(執行時需要獲取全局鎖)。
如果運行的線程大於或等於corePoolSize,那么就把task加入BlockQueue。
如果創建的線程數量大於BlockQueue的最大容量,那么創建新線程來執行該任務。
如果創建線程導致當前運行的線程數超過maximumPoolSize,就根據飽和策略來拒絕該任務。
關閉線程池
調用shutdown或者shutdownNow,兩者都不會接受新的任務,而且通過調用要停止線程的interrupt方法來中斷線程,有可能線程永遠不會被中斷,不同之處在於shutdownNow會首先將線程池的狀態設置為STOP,然后嘗試停止所有線程(有可能導致部分任務沒有執行完)然后返回未執行任務的列表。而shutdown則只是將線程池的狀態設置為shutdown,然后中斷所有沒有執行任務的線程,並將剩余的任務執行完。
配置線程個數
如果是CPU密集型任務,那么線程池的線程個數應該盡量少一些,一般為CPU的個數+1條線程。
如果是IO密集型任務,那么線程池的線程可以放的很大,如2*CPU的個數。
對於混合型任務,如果可以拆分的話,通過拆分成CPU密集型和IO密集型兩種來提高執行效率;如果不能拆分的的話就可以根據實際情況來調整線程池中線程的個數。
監控線程池狀態
常用狀態:
taskCount:線程需要執行的任務個數。
completedTaskCount:線程池在運行過程中已完成的任務數。
largestPoolSize:線程池曾經創建過的最大線程數量。
getPoolSize獲取當前線程池的線程數量。
getActiveCount:獲取活動的線程的數量
通過繼承線程池,重寫beforeExecute,afterExecute和terminated方法來在線程執行任務前,線程執行任務結束,和線程終結前獲取線程的運行情況,根據具體情況調整線程池的線程數量。
____________________________________________________________________________________________________________________________________________________________
ThreadPoolTaskExecutor 線程池簡單創建
https://www.jianshu.com/p/f2a7aff2e4c8
1.創建
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(5);//核心線程大小 executor.setMaxPoolSize(10);//最大線程大小 executor.setQueueCapacity(100);//隊列最大容量 executor.setKeepAliveSeconds(3000);//存活時間 executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());//拒絕執行時如何處理
2. 使用
executor.submit(new ThreadDemo());//或者executor.execute(new ThreadDemo()); // ---------------------------- public class ThreadDemo implements Runnable { @Override public void run() { //業務處理 } }
使用到這里已經結束,下面是一些相關說明
3. 執行過程
3.1 如果此時線程池中的數量小於corePoolSize,即使線程池中的線程都處於空閑狀態,也要創建新的線程來處理被添加的任務。
3.2 如果此時線程池中的數量等於 corePoolSize,但是緩沖隊列 workQueue未滿,那么任務被放入緩沖隊列。
3.3 如果此時線程池中的數量大於corePoolSize,緩沖隊列workQueue滿,並且線程池中的數量小於maxPoolSize,建新的線程來處理被添加的任務。
4.4 如果此時線程池中的數量大於corePoolSize,緩沖隊列workQueue滿,並且線程池中的數量等於maxPoolSize,那么通過handler所指定的策略來處理此任務。也就是:處理任務的優先級為:核心線程corePoolSize、任務隊列workQueue、最大線程 maximumPoolSize,如果三者都滿了,使用handler處理被拒絕的任務。
當線程池中的線程數量大於corePoolSize時,如果某線程空閑時間超過keepAliveTime,線程將被終止。這樣,線程池可以動態的調整池中的線程數。
4. 拒絕處理
4.1 ThreadPoolExecutor.AbortPolicy:丟棄任務並拋出RejectedExecutionException異常
4.2 ThreadPoolExecutor.DiscardPolicy:丟棄任務,但是不拋出異常。
4.3 ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列最前面。
4.4 ThreadPoolExecutor.CallerRunsPolicy:由調用者處理該任務 。
5. submit和execute區別
- 接收的參數不一樣
- submit有返回值,execute沒有返回值
- submit異常處理(future.get())
https://www.cnblogs.com/qq376324789/p/9729535.html
springboot中使用
@Configuration @EnableAsync public class Test02 { @Bean("taskModuleExecutor") ThreadPoolTaskExecutor getCrawler1(){ ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor(); threadPoolTaskExecutor.setCorePoolSize(3); threadPoolTaskExecutor.setMaxPoolSize(10); threadPoolTaskExecutor.setQueueCapacity(200); threadPoolTaskExecutor.setThreadNamePrefix("task-concurrent-work"); threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); threadPoolTaskExecutor.initialize(); return threadPoolTaskExecutor; } }
@Resource(name="taskModuleExecutor") private TaskExecutor taskExecutor;
一般實際開發中經常用到多線程,所以需要使用線程池了,
ThreadPoolTaskExecutor通常通過XML方式配置,或者通過Executors
的工廠方法進行配置。
XML方式配置代碼如下:交給spring來管理;
<bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"> <!-- 核心線程數 --> <property name="corePoolSize" value="4000" /> <!-- 最大線程數 --> <property name="maxPoolSize" value="20000" /> <!-- 隊列最大長度 --> <property name="queueCapacity" value="2000" /> <!-- 線程池維護線程所允許的空閑時間 --> <property name="keepAliveSeconds" value="30" /> <!-- 線程池對拒絕任務(無線程可用)的處理策略 --> <property name="rejectedExecutionHandler"> <bean class="java.util.concurrent.ThreadPoolExecutor$DiscardPolicy" /> </property> </bean>
rejectedExecutionHandler
字段用於配置拒絕策略,常用的拒絕策略如下:
AbortPolicy,用於被拒絕任務的處理程序,它將拋出RejectedExecutionException。
CallerRunsPolicy,用於被拒絕任務的處理程序,它直接在execute方法的調用線程中運行被拒絕的任務。
DiscardOldestPolicy,用於被拒絕任務的處理程序,它放棄最舊的未處理請求,然后重試execute
。
DiscardPolicy,用於被拒絕任務的處理程序,默認情況下它將丟棄被拒絕的任務。
提交任務
無返回值的任務使用execute(Runnable)
有返回值的任務使用submit(Runnable)
案例代碼
threadPoolTaskExecutor.execute(new Runnable() { public void run() { synchronized (Controller01.class) { try { HttpUtils.get("http://192.168.31.223:8085/test2.do"); System.out.println(System.currentTimeMillis()); } catch (Exception e) { e.printStackTrace(); } } } });
任務處理流程:
- 當一個任務被提交到線程池時,首先查看線程池的核心線程是否都在執行任務,否就選擇一條線程執行任務,是就執行第二步。
- 查看核心線程池是否已滿,不滿就創建一條線程執行任務,否則執行第三步。
- 查看任務隊列是否已滿,不滿就將任務存儲在任務隊列中,否則執行第四步。
- 查看線程池是否已滿,不滿就創建一條線程執行任務,否則就按照策略處理無法執行的任務。
在ThreadPoolExecutor中表現為:
- 如果當前運行的線程數小於corePoolSize,那么就創建線程來執行任務(執行時需要獲取全局鎖)。
- 如果運行的線程大於或等於corePoolSize,那么就把task加入BlockQueue。
- 如果創建的線程數量大於BlockQueue的最大容量,那么創建新線程來執行該任務。
- 如果創建線程導致當前運行的線程數超過maximumPoolSize,就根據飽和策略來拒絕該任務。
關閉線程池
調用shutdown或者shutdownNow,
兩者都不會接受新的任務,而且通過調用要停止線程的interrupt方法來中斷線程,有可能線程永遠不會被中斷,
不同之處在於shutdownNow會首先將線程池的狀態設置為STOP,然后嘗試停止所有線程(有可能導致部分任務沒有執行完)然后返回未執行任務的列表。
而shutdown則只是將線程池的狀態設置為shutdown,然后中斷所有沒有執行
任務的線程,並將剩余的任務執行完。
常用狀態:
- taskCount:線程需要執行的任務個數。
- completedTaskCount:線程池在運行過程中已完成的任務數。
- largestPoolSize:線程池曾經創建過的最大線程數量。
- getPoolSize獲取當前線程池的線程數量。
- getActiveCount:獲取活動的線程的數量
通過繼承線程池,重寫beforeExecute
,afterExecute
和terminated
方法來在線程執行任務前,線程執行任務結束,和線程終結前獲取線程的運行情況,根據具體情況調整線程池的線程數量
使用場景
1、當你的任務是非必要的時候。比如記錄操作日志、通知第三方服務非必要信息等,可以使用線程池處理非阻塞任務
2、當你的任務非常耗時時候,可以采用線程池技術
3、當請求並發很高時,可以采用線程池技術優化處理
多線程是不是能加快處理速度?
在使用多線程時,一定要知道一個道理:處理速度的最終決定因素是CPU、內存等,在單CPU(無論多少核)上,分配CPU資源的單位是“進程”而不是“線程”。
我們可以做一個簡單的試驗:
假設我要拷貝100萬條數據,單CPU電腦,用一個進程,在單線程的情況下,CPU占用率為5%,耗時1000秒。那么當在這個進程下,開辟10個線程同時去運行,是不是CPU占用率增加到50%,耗時減少到100秒呢?顯然不是。我實測出來的情況是這樣的:
“CPU占用率仍然是5%,總耗時仍然是1000秒。且每個線程的運行時間也為1000秒。”
總結:
第一,
看硬件。如果是在比較強大的、多CPU的服務器上運行程序,可以使用多線程來提高並發數和執行速度。
但是線程也不宜過多,即使是16個CPU的服務器,同一時間最多也只能真正意義上地並發處理16個線程,多出來的線程還是要等待。
第二,
看用途。如果你不在乎處理速度,僅僅是為了提高並發處理能力,那么理所當然地用多線程,
但是如果你僅僅是想提高處理速度,且又是在單CPU機器上運行,那么多線程並不值得。
如果你的任務很耗時,且可以一部分、一部分地做,那么最好不要用多線程(好比搬 磚,單線程一次搬10塊,總共搬10天,但搬一塊算一塊,到第9天的時候,你就搬完90塊磚了;
如果你用10個線程同時去搬磚,同樣要搬10天,但是到第9天的時候,這10個線程100塊磚都“還在路上”,一塊磚都沒搬完!)。