代碼實例理解線程池的配置屬性

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,直接拋出異常。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章