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

併發編程系列:

高併發編程系列:4種經常使用Java線程鎖的特色,性能比較、使用場景html

線程池的原因

java中爲了提升併發度,可使用多線程共同執行,可是若是有大量線程短期以內被建立和銷燬,會佔用大量的系統時間,影響系統效率。java

爲了解決上面的問題,java中引入了線程池,可使建立好的線程在指定的時間內由系通通一管理,而不是在執行時建立,執行後就銷燬,從而避免了頻繁建立、銷燬線程帶來的系統開銷。數據庫

 

線程池如何使用,以及實現原理,處理步驟,有什麼使用注意事項等,今天主要從這幾個方面詳細介紹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
相關文章
相關標籤/搜索