合理利用線程池能夠帶來三個好處
第一:降低資源消耗。通過重復利用已創建的線程降低線程創建和銷毀造成的消耗。
第二:提高響應速度。當任務到達時,任務可以不需要的等到線程創建就能立即執行。
第三:提高線程的可管理性。線程是稀缺資源,如果無限制的創建,不僅會消耗系統資源,還會降低系統的穩定性,使用線程池可以進行統一的分配,調優和監控。但是要做到合理的利用線程池,必須對其原理了如指掌
線程池的主要工作流程
執行邏輯說明:
判斷核心線程數是否已滿,核心線程數大小和corePoolSize參數有關,未滿則創建線程執行任務
若核心線程池已滿,判斷隊列是否滿,隊列是否滿和workQueue參數有關,若未滿則加入隊列中
若隊列已滿,判斷線程池是否已滿,線程池是否已滿和maximumPoolSize參數有關,若未滿創建線程執行任務
若線程池已滿,則采用拒絕策略處理無法執執行的任務,拒絕策略和handler參數有關
線程池的創建
我們可以通過ThreadPoolExecutor來創建一個線程池。
new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, milliseconds,runnableTaskQueue, threadFactory,handler);
創建一個線程池需要輸入幾個參數:
corePoolSize - 線程池核心池的大小。 maximumPoolSize - 線程池的最大線程數。 keepAliveTime - 當線程數大於核心時,此為終止前多余的空閑線程等待新任務的最長時間。 unit - keepAliveTime 的時間單位。 workQueue - 用來儲存等待執行任務的隊列。 threadFactory - 線程工廠。 handler - 拒絕策略。
參數的英文解釋:
* @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.
輕松理解corePoolSize與maxPoolSize:
我們可以把 corePoolSize 與 maxPoolSize 比喻成長工與臨時工,通常古代一個大戶人家會有幾個固定的長工,負責日常的工作,而大戶人家起初肯定也是從零開始雇佣長工的。
假如長工數量被老爺設定為 5 人,也就對應了 corePoolSize,不管這 5 個長工是忙碌還是空閑,都會一直在大戶人家待着,可到了農忙或春節,長工的人手顯然就不夠用了,這時就需要雇佣更多的臨時工,這些臨時工就相當於在 corePoolSize 的基礎上繼續創建新線程,但臨時工也是有上限的,也就對應了maxPoolSize,隨着農忙或春節結束,老爺考慮到人工成本便會解約掉這些臨時工,家里工人數量便會從maxPoolSize降到corePoolSize,所以老爺家的工人數量會一致保持在corePoolSize 和 maxPoolSize 的區間。
為什么線程池不允許使用Executors去創建
阿里巴巴java開發手冊有這么一句話:“線程池不允許使用Executors去創建,而是通過ThreadPoolExecutor的方式,這樣的處理方式讓寫的同學更加明確線程池的運行規則,規避資源耗盡的風險”
Executors創建返回ThreadPoolExecutor對象的方法共有三種:
Executors#newCachedThreadPool => 創建可緩存的線程池 Executors#newSingleThreadExecutor => 創建單線程的線程池 Executors#newFixedThreadPool => 創建固定長度的線程池
Executors#newCachedThreadPool方法
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
CachedThreadPool是一個根據需要創建新線程的線程池
corePoolSize => 0,核心線程池的數量為0 maximumPoolSize => Integer.MAX_VALUE,可以認為最大線程數是無限的 keepAliveTime => 60L unit => 秒 workQueue => SynchronousQueue
當一個任務提交時,corePoolSize為0不創建核心線程,SynchronousQueue是一個不存儲元素的隊列,可以理解為隊里永遠是滿的,因此最終會創建非核心線程來執行任務。
對於非核心線程空閑60s時將被回收。因為Integer.MAX_VALUE非常大,可以認為是可以無限創建線程的,在資源有限的情況下容易引起OOM異常
Executors#newSingleThreadExecutor方法
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }
SingleThreadExecutor是單線程線程池,只有一個核心線程
corePoolSize => 1,核心線程池的數量為1 maximumPoolSize => 1,只可以創建一個非核心線程 keepAliveTime => 0L unit => 秒 workQueue => LinkedBlockingQueue
當一個任務提交時,首先會創建一個核心線程來執行任務,如果超過核心線程的數量,將會放入隊列中,因為LinkedBlockingQueue是長度為Integer.MAX_VALUE的隊列,可以認為是無界隊列,因此往隊列中可以插入無限多的任務,在資源有限的時候容易引起OOM異常,同時因為無界隊列,maximumPoolSize和keepAliveTime參數將無效,壓根就不會創建非核心線程
Executors#newFixedThreadPool方法
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
FixedThreadPool是固定核心線程的線程池,固定核心線程數由用戶傳入
corePoolSize => 1,核心線程池的數量為1 maximumPoolSize => 1,只可以創建一個非核心線程 keepAliveTime => 0L unit => 秒 workQueue => LinkedBlockingQueue 它和SingleThreadExecutor類似,唯一的區別就是核心線程數不同,並且由於使用的是LinkedBlockingQueue,在資源有限的時候容易引起OOM異常
總結:
FixedThreadPool和SingleThreadExecutor => 允許的請求隊列長度為Integer.MAX_VALUE,可能會堆積大量的請求,從而引起OOM異常
CachedThreadPool => 允許創建的線程數為Integer.MAX_VALUE,可能會創建大量的線程,從而引起OOM異常
這就是為什么禁止使用Executors去創建線程池,而是推薦自己去創建ThreadPoolExecutor的原因
線程池創建方式(推薦)
根據阿里巴巴java開發規范,推薦了3種線程池創建方式
推薦方式1:
首先引入:commons-lang3包
ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1, new BasicThreadFactory.Builder().namingPattern("example-schedule-pool-%d").daemon(true).build());
推薦方式 2:
首先引入:com.google.guava包
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder() .setNameFormat("demo-pool-%d").build(); //Common Thread Pool ExecutorService pool = new ThreadPoolExecutor(5, 200, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy()); pool.execute(()-> System.out.println(Thread.currentThread().getName())); pool.shutdown();//gracefully shutdown
推薦方式 3:
spring配置線程池方式:自定義線程工廠bean需要實現ThreadFactory,可參考該接口的其它默認實現類,使用方式直接注入bean
調用execute(Runnable task)方法即可
<bean id="userThreadPool" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"> <property name="corePoolSize" value="10" /> <property name="maxPoolSize" value="100" /> <property name="queueCapacity" value="2000" /> <property name="threadFactory" value= threadFactory /> <property name="rejectedExecutionHandler"> <ref local="rejectedExecutionHandler" /> </property> </bean> //in code userThreadPool.execute(thread);