【多线程】线程池的创建和参数设定
為什么要使用線程池
在日常開發中為了提高代碼運行效率,或多或少會用線程去執行異步任務,線程的創建和銷毀是需要占用一定資源的。
首先我們看一下一個線程的創建步驟:
- 為線程堆棧分配和初始化大量內存塊
- 需要進行系統調用,以便在主機OS中創建/注冊本機線程
- 描述符需要創建、初始化并添加到JVM內部數據結構中
而池化技術的出現是為了重復利用已存在的線程,避免了頻繁的創建和銷毀。
線程池的初始化及參數
注意:線程池必須手動通過 ThreadPoolExecutor 的構造函數來聲明,避免使用Executors 類的 newFixedThreadPool 和 newCachedThreadPool ,因為可能會有 OOM 的風險。
看下ThreadPoolExecutor中的構造方法
/*** Creates a new {@code ThreadPoolExecutor} with the given initial* parameters.** @param corePoolSize the number of threads to keep in the pool, even* if they are idle, unless {@code allowCoreThreadTimeOut} is set* @param maximumPoolSize the maximum number of threads to allow in the* pool* @param keepAliveTime when the number of threads is greater than* the core, this is the maximum time that excess idle threads* will wait for new tasks before terminating.* @param unit the time unit for the {@code keepAliveTime} argument* @param workQueue the queue to use for holding tasks before they are* executed. This queue will hold only the {@code Runnable}* tasks submitted by the {@code execute} method.* @param threadFactory the factory to use when the executor* creates a new thread* @param handler the handler to use when execution is blocked* because the thread bounds and queue capacities are reached* @throws IllegalArgumentException if one of the following holds:<br>* {@code corePoolSize < 0}<br>* {@code keepAliveTime < 0}<br>* {@code maximumPoolSize <= 0}<br>* {@code maximumPoolSize < corePoolSize}* @throws NullPointerException if {@code workQueue}* or {@code threadFactory} or {@code handler} is null*/public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler) {if (corePoolSize < 0 ||maximumPoolSize <= 0 ||maximumPoolSize < corePoolSize ||keepAliveTime < 0)throw new IllegalArgumentException();if (workQueue == null || threadFactory == null || handler == null)throw new NullPointerException();this.corePoolSize = corePoolSize;this.maximumPoolSize = maximumPoolSize;this.workQueue = workQueue;this.keepAliveTime = unit.toNanos(keepAliveTime);this.threadFactory = threadFactory;this.handler = handler;}corePoolSize 核心線程數
核心線程:要保留在池中的線??程數,即使它們處于空閑狀態,線程池創建的時候是空的,隨著任務的調用,逐步增加到核心線程數的大小。當任務進來時,如果有核心線程空閑,則優先使用核心線程。
如何設計核心線程池的數量:
- 如果是CPU密集型應用,則線程池大小設置為N+1 (N為CPU總核數)
- 如果是IO密集型應用,則線程池大小設置為2N+1 (N為CPU總核數)
- 線程等待時間(IO)所占比例越高,需要越多線程。
- 線程CPU時間所占比例越高,需要越少線程。
1. 題外話:cpu密集型和IO密集型是什么
是任務、方法的類型
1. cpu密集型(計算密集型、cpu高了)
處理運算時間比較長,系統運行的大部分狀況是CPU Loading 100%,不太需要訪問I/O設備
1. 例如
計算圓周率、對視頻進行高清解碼
2. 要注意什么
盡量避免CPU的切換,任務同時進行的數量 = CPU的核心數
1. IO密集型
IO的速度遠遠低于CPU和內存的速度,cpu性能好,處理運算時間比較短,大部分的狀況是CPU在等I/O (硬盤/內存) 的讀/寫操作
1. 例如
Web應用
2. 要注意什么
可以充分利用CPU的資源,但不能開啟任務數量太多,一般情況:任務同時進行的數量 = 2*CPU的核心數
maximumPoolSize 最大線程數
池中允許的最大線程數
keepAliveTime 非核心線程的活躍時間
當線程數大于核心數時,非核心線程如果過了keepAliveTime長時間還沒有執行新的任務,則銷毀線程。
unit 單位
是keepAliveTime的時間單位
workQueue 隊列
用于在執行任務之前保存任務的隊列。該隊列將僅保存由 {@code execute} 方法提交的 {@code Runnable} 任務。
workQueue的類型為BlockingQueue,通常可以取下面三種類型:
隊列的種類:
- ArrayBlockingQueue 有界任務隊列
基于數組的先進先出隊列,此隊列創建時必須指定大小;FIFO
- LinkedBlockingQueue 無界任務隊列
基于鏈表的先進先出隊列,如果創建時沒有指定此隊列大小,則默認為Integer.MAX_VALUE;FIFO
- SynchronousQueue 同步隊列
一個不存儲元素的阻塞隊列,每個插入操作必須等到另一個線程調用移除操作,否則插入操作一直處于阻塞狀態,吞吐量通常要高于LinkedBlockingQuene
- PriorityBlockingQueue 優先級隊列
一個支持優先級的無界阻塞隊列,可以通過其構造方法,自定義排序的規則,默認按照首字母從小到大排序,生產的時候隨便,消費的時候會按照優先級消費
public PriorityBlockingQueue(int initialCapacity,Comparator<? super E> comparator) {if (initialCapacity < 1)throw new IllegalArgumentException();this.lock = new ReentrantLock();this.notEmpty = lock.newCondition();this.comparator = comparator;this.queue = new Object[initialCapacity];}threadFactory 線程工廠
工廠類,創建新線程時使用的工廠
handler 飽和策略
由于達到線程邊界和隊列容量而阻塞執行時要使用的處理程序,默認是AbordPolicy,表示無法處理新任務,并拋出 RejectedExecutionException 異常
1. AbortPolicy(拒絕拋出異常)
/*** A handler for rejected tasks that throws a* {@code RejectedExecutionException}.*/public static class AbortPolicy implements RejectedExecutionHandler {}丟棄任務并拋出RejectedExecutionException異常,默認策略
如果是比較關鍵的業務,推薦使用此拒絕策略,這樣子在系統不能承載更大的并發量的時候,能夠及時的通過異常發現。
2. CallerRunsPolicy(讓外面的線程去處理)
/*** A handler for rejected tasks that runs the rejected task* directly in the calling thread of the {@code execute} method,* unless the executor has been shut down, in which case the task* is discarded.*/public static class CallerRunsPolicy implements RejectedExecutionHandler {}由調用線程(執行execute的線程)去處理該任務,如果調用線程關閉,則直接拋棄該任務
3. DiscardOldestPolicy(把最老的丟掉、喜新厭舊)
/*** A handler for rejected tasks that discards the oldest unhandled* request and then retries {@code execute}, unless the executor* is shut down, in which case the task is discarded.*/public static class DiscardOldestPolicy implements RejectedExecutionHandler {}丟棄隊列最老的未執行的任務,然后重新提交被拒絕的這個任務(喜新厭舊的策略)
是否要采用此種拒絕策略,還得根據實際業務是否允許丟棄老任務來認真衡量。
4. DiscardPolicy(偷偷的拋棄,不拋異常)
/*** A handler for rejected tasks that silently discards the* rejected task.*/public static class DiscardPolicy implements RejectedExecutionHandler {}直接拋棄該任務,不會拋出異常。使用該策略,可能會使我們無法發現系統的異常狀態。建議是一些無關緊要的業務采用此策略。
1.例如
閱讀量、點擊量這些高頻的但不需要很精確操作
總結
以上是生活随笔為你收集整理的【多线程】线程池的创建和参数设定的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 请画出使用mapreduce对英文句子_
- 下一篇: SpringCloud(四)Eureka
