高併發編程系列:4種經常使用Java線程鎖的特色,性能比較、使用場景html
java中爲了提升併發度,可使用多線程共同執行,可是若是有大量線程短期以內被建立和銷燬,會佔用大量的系統時間,影響系統效率。java
爲了解決上面的問題,java中引入了線程池,可使建立好的線程在指定的時間內由系通通一管理,而不是在執行時建立,執行後就銷燬,從而避免了頻繁建立、銷燬線程帶來的系統開銷。數據庫
線程池如何使用,以及實現原理,處理步驟,有什麼使用注意事項等,今天主要從這幾個方面詳細介紹Java線程池。編程
就以ThreadPoolExecutor爲例,當咱們把一個Runnable交給線程池去執行的時候,這個線程池處理的流程是這樣的:數組
在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(任務隊列)
用於保存等待執行的任務的阻塞隊列。能夠選擇如下幾個阻塞隊列。
3.maximumPoolSize(線程池最大大小)
線程池容許建立的最大線程數。若是隊列滿了,而且已建立的線程數小於最大線程數,則線程池會再建立新的線程執行任務。值得注意的是若是使用了無界的任務隊列這個參數就沒什麼效果。
4.ThreadFactory:用於設置建立線程的工廠
能夠經過線程工廠給每一個建立出來的線程設置更有意義的名字,Debug和定位問題時很是又幫助。
5.RejectedExecutionHandler(飽和策略)
當隊列和線程池都滿了,說明線程池處於飽和狀態,那麼必須採起一種策略處理提交的新任務。這個策略默認狀況下是AbortPolicy,表示沒法處理新任務時拋出異常。如下是JDK1.5提供的四種策略。n AbortPolicy:直接拋出異常。
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數據庫的優化、異步消息等