線程池為什么不允許使用Executors創建


合理利用線程池能夠帶來三個好處

第一:降低資源消耗。通過重復利用已創建的線程降低線程創建和銷毀造成的消耗。

第二:提高響應速度。當任務到達時,任務可以不需要的等到線程創建就能立即執行。
第三:提高線程的可管理性。線程是稀缺資源,如果無限制的創建,不僅會消耗系統資源,還會降低系統的穩定性,使用線程池可以進行統一的分配,調優和監控。但是要做到合理的利用線程池,必須對其原理了如指掌

線程池的主要工作流程

 執行邏輯說明:

判斷核心線程數是否已滿,核心線程數大小和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);

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM