Java線程池的使用方式,核心運行原理、以及注意事項

線程池的處理流程

 

就以ThreadPoolExecutor爲例,當我們把一個Runnable交給線程池去執行的時候,這個線程池處理的流程是這樣的:

併發編程系列:線程池的使用方式,核心運行原理、以及注意事項

  1.  先判斷線程池中的核心線程們是否空閒,如果空閒,就把這個新的任務指派給某一個空閒線程去執行。如果沒有空閒,並且當前線程池中的核心線程數還小於 corePoolSize,那就再創建一個核心線程。
  2.  如果線程池的線程數已經達到核心線程數,並且這些線程都繁忙,就把這個新來的任務放到等待隊列中去。如果等待隊列又滿了,那麼查看一下當前線程數是否到達maximumPoolSize,如果還未到達,就繼續創建線程。
  3.  如果已經到達了,就交給RejectedExecutionHandler(拒絕策略)來決定怎麼處理這個任務。

線程池的使用(ThreadPoolExecutor)

在Java中,線程池的概念是Executor這個接口,具體實現爲ThreadPoolExecutor類,是線程池中最核心的一個類,因此如果要透徹地瞭解Java中的線程池,必須先了解這個類。

ThreadPoolExecutor繼承了AbstractExecutorService類,並提供了四個構造器

public class ThreadPoolExecutor extends AbstractExecutorService {

…..

public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,

BlockingQueue<Runnable> workQueue);

public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,

BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory);

public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,

BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);

public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,

BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);

}

ThreadPoolExecutor繼承了AbstractExecutorService類,並提供了四個構造器,事實上,通過觀察每個構造器的源碼具體實現,發現前面三個構造器都是調用的第四個構造器進行的初始化工作。

下面解釋下一下構造器中各個參數的含義:

 

1.corePoolSize(線程池的基本大小)

當提交一個任務到線程池時,線程池會創建一個線程來執行任務,即使其他空閒的基本線程能夠執行新任務也會創建線程,等到需要執行的任務數大於線程池基本大小時就不再創建。如果調用了線程池的prestartAllCoreThreads方法,線程池會提前創建並啓動所有基本線程。

 

2.runnableTaskQueue(任務隊列)

用於保存等待執行的任務的阻塞隊列。可以選擇以下幾個阻塞隊列。

  •  ArrayBlockingQueue:是一個基於數組結構的有界阻塞隊列,此隊列按 FIFO(先進先出)原則對元素進行排序。
  •  LinkedBlockingQueue:一個基於鏈表結構的阻塞隊列,此隊列按FIFO (先進先出) 排序元素,吞吐量通常要高於ArrayBlockingQueue。
  •  SynchronousQueue:一個不存儲元素的阻塞隊列。每個插入操作必須等到另一個線程調用移除操作,否則插入操作一直處於阻塞狀態,吞吐量通常要高於LinkedBlockingQueue。
  •  PriorityBlockingQueue:一個具有優先級得無限阻塞隊列。

3.maximumPoolSize(線程池最大大小)

線程池允許創建的最大線程數。如果隊列滿了,並且已創建的線程數小於最大線程數,則線程池會再創建新的線程執行任務。值得注意的是如果使用了無界的任務隊列這個參數就沒什麼效果。

 

4.ThreadFactory:用於設置創建線程的工廠

可以通過線程工廠給每個創建出來的線程設置更有意義的名字,Debug和定位問題時非常又幫助。

 

5.RejectedExecutionHandler(飽和策略)

當隊列和線程池都滿了,說明線程池處於飽和狀態,那麼必須採取一種策略處理提交的新任務。這個策略默認情況下是AbortPolicy,表示無法處理新任務時拋出異常。以下是JDK1.5提供的四種策略。n AbortPolicy:直接拋出異常。

  •  CallerRunsPolicy:只用調用者所在線程來運行任務。
  •  DiscardOldestPolicy:丟棄隊列裏最近的一個任務,並執行當前任務。
  •  DiscardPolicy:不處理,丟棄掉。
  •  當然也可以根據應用場景需要來實現RejectedExecutionHandler接口自定義策略。如記錄日誌或持久化不能處理的任務。

6.keepAliveTime(線程活動保持時間)

線程池的工作線程空閒後,保持存活的時間。所以如果任務很多,並且每個任務執行的時間比較短,可以調大這個時間,提高線程的利用率。

 

7.TimeUnit(線程活動保持時間的單位)

可選的單位有天(DAYS),小時(HOURS),分鐘(MINUTES),毫秒(MILLISECONDS),微秒(MICROSECONDS, 千分之一毫秒)和毫微秒(NANOSECONDS, 千分之一微秒)。

線程池的注意事項

雖然線程池能大大提高服務器的併發性能,但使用它也會存在一定風險。與所有多線程應用程序一樣,用線程池構建的應用程序容易產生各種併發問題,如對共享資源的競爭和死鎖。此外,如果線程池本身的實現不健壯,或者沒有合理地使用線程池,還容易導致與線程池有關的死鎖、系統資源不足和線程泄漏等問題。

 

1) 建議使用new ThreadPoolExecutor(…)的方式創建線程池

線程池的創建不應使用
Executors 去創建,而應該通過 ThreadPoolExecutor
創建,這樣可以讓讀者更加明確地知道線程池的參數設置、運行規則,規避資源耗盡的風險,這一點在也阿里巴巴JAVA開發手冊中也有明確要求。這一點不容小覷,曾有同學因爲線程池使用不當導致生產的同一臺機器上部署的多個應用都因無法創建線程池而出現故障。

 

2) 合理設置線程數

線程池的工作線程數設置應根據實際情況配置,CPU密集型業務(搜索、排序等)CPU空閒時間較少,線程數不能設置太多。

如果是CPU密集型任務,就需要儘量壓榨CPU,參考值可以設爲 NCPU+1

如果是IO密集型任務,參考值可以設置爲2*NCPU

 

3) 設置能代表具體業務的線程名稱

這樣方便通過日誌的線程名稱識別所屬業務。具體實現可以通過指定ThreadPoolExecutor的ThreadFactory參數。如使Spring提供的CustomizableThreadFactory。

以上就是Java線程池的詳細介紹,除了從編程的角度應對高併發,更多還需要從架構設計的層面來應對高併發場景,例如:Redis緩存、MySQL數據庫的優化、異步消息等

併發編程系列:線程池的使用方式,核心運行原理、以及注意事項

原文鏈接:http://youzhixueyuan.com/use-of-java-thread-pool.html

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