Java多线程系列(三):Java线程池的使用方式,及核心运行原理
之前談過多線程相關的4種常用Java線程鎖的特點,性能比較、使用場景,今天主要分享線程池相關的內容,這些都是屬于Java面試的必考點。
為什么需要線程池
java中為了提高并發度,可以使用多線程共同執行,但是如果有大量線程短時間之內被創建和銷毀,會占用大量的系統時間,影響系統效率。
為了解決上面的問題,java中引入了線程池,可以使創建好的線程在指定的時間內由系統統一管理,而不是在執行時創建,執行后就銷毀,從而避免了頻繁創建、銷毀線程帶來的系統開銷。
線程池如何使用,以及實現原理,處理步驟,有什么使用注意事項等,今天主要從這幾個方面詳細介紹Java線程池。
線程池的處理流程
就以ThreadPoolExecutor為例,當我們把一個Runnable交給線程池去執行的時候,這個線程池處理的流程是這樣的:
線程池的使用(ThreadPoolExecutor)
在Java中,線程池的概念是Executor這個接口,具體實現為ThreadPoolExecutor類,是線程池中最核心的一個類,因此如果要透徹地了解Java中的線程池,必須先了解這個類。
ThreadPoolExecutor繼承了AbstractExecutorService類,并提供了四個構造器:
public class ThreadPoolExecutor extends AbstractExecutorService {
…..
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue workQueue);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue workQueue,ThreadFactory threadFactory);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue workQueue,RejectedExecutionHandler handler);
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
BlockingQueue 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多线程系列(三):Java线程池的使用方式,及核心运行原理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Docker系列之一:入门介绍
- 下一篇: 论文浅尝 | DRUM:一种端到端的可微