1.合理利用線程池能夠帶來三個好處:
- 第一:降低資源消耗。通過重複利用已創建的線程降低線程創建和銷燬造成的消耗。
- 第二:可有效控制最大併發線程數,提高系統資源利用率,同時可以避免過多的資源競爭,避免阻塞。
- 第三:提供定時執行、定期執行、單線程、併發數等控制。
2.使用方式之一:
3.參數說明
1)corePoolSize(線程池的基本大小,核心線程數量):
當提交一個任務到線程池時,線程池會創建一個線程來執行任務,即使其他空閒的基本線程能夠執行新任務也會創建線程,等到需要執行的任務數大於核心線程的數量時就不再創建。如果調用了線程池的prestartAllCoreThreads方法,線程池會提前創建並啓動所有基本線程。
2)maximumPoolSize(線程池最大大小):
線程池允許創建的最大線程數。如果隊列滿了,並且已創建的線程數小於最大線程數,則線程池會再創建新的線程執行任務。值得注意的是如果使用了無界的任務隊列這個參數就沒什麼效果。
3)keepAliveTime(線程活動保持時間):
線程池的工作線程空閒後(指大於核心又小於max的那部分線程),保持存活的時間。所以如果任務很多,並且每個任務執行的時間比較短,可以調大這個時間,讓空閒線程多活一會,提高線程的利用率。
4)TimeUnit(線程活動保持時間的單位):
可選的單位有天(DAYS),小時(HOURS),分鐘(MINUTES),毫秒(MILLISECONDS),微秒(MICROSECONDS, 千分之一毫秒)和毫微秒(NANOSECONDS, 千分之一微秒)。
5)runnableTaskQueue(任務隊列):
用於保存等待執行的任務的阻塞隊列。
在重複一下新任務進入時線程池的執行策略:
如果運行的線程少於corePoolSize,則 Executor始終首選添加新的線程,而不進行排隊。(如果當前運行的線程小於corePoolSize,則任務根本不會存入queue中,而是直接運行)
如果運行的線程大於等於 corePoolSize,則 Executor始終首選將請求加入隊列,而不添加新的線程。
如果無法將請求加入隊列,則創建新的線程,除非創建此線程超出 maximumPoolSize,在這種情況下,任務將被拒絕。
主要有3種類型的BlockingQueue:
無界隊列
隊列大小無限制,常用的爲無界的LinkedBlockingQueue,使用該隊列做爲阻塞隊列時要尤其當心,當任務耗時較長時可能會導致大量新任務在隊列中堆積最終導致OOM。閱讀代碼發現,Executors.newFixedThreadPool 採用就是 LinkedBlockingQueue,當QPS很高,發送數據很大,大量的任務被添加到這個無界LinkedBlockingQueue 中,會導致cpu和內存飆升服務器掛掉。
有界隊列
常用的有兩類,一類是遵循FIFO(先進先出)原則的隊列如ArrayBlockingQueue與有界的LinkedBlockingQueue,另一類是優先級隊列如PriorityBlockingQueue。PriorityBlockingQueue中的優先級由任務的Comparator決定。
使用有界隊列時隊列大小需和線程池大小互相配合,線程池較小有界隊列較大時可減少內存消耗,降低cpu使用率和上下文切換,但是可能會限制系統吞吐量。
同步移交隊列
如果不希望任務在隊列中等待而是希望將任務直接移交給工作線程,可使用SynchronousQueue作爲等待隊列。SynchronousQueue不是一個真正的隊列,而是一種線程之間移交的機制。要將一個元素放入SynchronousQueue中,必須有另一個線程正在等待接收這個元素。只有在使用無界線程池或者有飽和策略時才建議使用該隊列。
6)ThreadFactory:
用於設置創建線程的工廠,可以通過線程工廠給每個創建出來的線程設置更有意義的名字。
7)RejectedExecutionHandler(飽和策略):
當隊列和線程池都滿了,說明線程池處於飽和狀態,那麼必須採取一種策略處理提交的新任務。這個策略默認情況下是AbortPolicy,表示無法處理新任務時拋出異常。以下是JDK1.5提供的四種策略。
AbortPolicy:直接拋出異常。
CallerRunsPolicy:將任務回退給調用者來直接運行。
DiscardOldestPolicy:丟棄隊列裏最近的一個任務,並執行當前任務。
DiscardPolicy:不處理,丟棄掉。
自定義:當然也可以根據應用場景需要來實現RejectedExecutionHandler接口自定義策略。如記錄日誌或持久化不能處理的任務。
4.代碼驗證
之前整理了以上關於線程池的內容,由於一直沒有用到線程池,對於裏面參數的理解沒有一個具體的概念。正好有個項目用到了@Async註解,需要自定義線程池,因此就用代碼加深了對參數的理解。
異步方法類:
@Component
public class Print {
@Async("asyncThreadPoolExecutor")
public void aysncPrint(){
System.out.println("--- async: thread name: " + Thread.currentThread().getName());
}
}
調用異步方法的類:
@Component
public class AsyncDemo {
@Autowired
private Print print;
public void test() {
int number = 10;
for (int i = 1; i <= number; i++) {
//異步方法
print.aysncPrint();
}
}
}
測試類:
@RunWith(SpringRunner.class)
@SpringBootTest
public class AsyncTest {
@Autowired
private AsyncDemo asyncDemo;
@Test
public void test(){
asyncDemo.test();
}
}
線程池主要有以下配置:
spring.task.execution.pool.core-threads = 3
spring.task.execution.pool.max-threads = 5
spring.task.execution.pool.queue-capacity = 100
spring.task.execution.pool.keep-alive = 10
@Configuration
public class ThreadsConfig implements AsyncConfigurer {
@Value("${spring.task.execution.pool.core-threads}")
private int corePoolSize;
@Value("${spring.task.execution.pool.max-threads}")
private int maxPoolSize;
@Value("${spring.task.execution.pool.queue-capacity}")
private int queueCapacity;
@Value("${spring.task.execution.pool.keep-alive}")
private int keepAliveSeconds;
@Bean("asyncThreadPoolExecutor")
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor threadPool = new ThreadPoolTaskExecutor();
//設置核心線程數
threadPool.setCorePoolSize(corePoolSize);
//設置最大線程數
threadPool.setMaxPoolSize(maxPoolSize);
//線程池所使用的緩衝隊列
threadPool.setQueueCapacity(queueCapacity);
//等待任務在關機時完成--表明等待所有線程執行完
threadPool.setWaitForTasksToCompleteOnShutdown(true);
// 等待時間 (默認爲0,此時立即停止),並沒等待xx秒後強制停止
threadPool.setAwaitTerminationSeconds(keepAliveSeconds);
// 初始化線程
threadPool.initialize();
return threadPool;
}
}
當核心線程爲3,最大線程爲5,AsyncDemo類test方法number屬性爲1(執行1次異步方法調用)時,輸出如下:
--- async: thread name: asyncThreadPoolExecutor-1
可以看出線程池只安排了一個線程asyncThreadPoolExecutor-1執行任務。
便於展示,根據實驗得出以下表格:
結論:當任務到達時,如果當前運行的線程少於corePoolSize,則 Executor始終首選添加新的線程執行任務,而不是把任務放入隊列。當創建的線程數大於等於 corePoolSize,則 Executor始終首選將任務加入隊列,而不添加新的線程,任務由核心線程執行。當線程池裏核心線程和隊列都滿時(大於實驗中核心線程3個+100個任務 ==103)才創建新的線程執行任務。但當創建的線程大於最大線程數+任務數時(106),由於設置的飽和策略是默認的AbortPolicy,直接拋出異常。