關於線程池你應該瞭解的

爲何使用線程池

線程池的工做主要是控制運行的線程數量,處理過程當中將任務放入隊列,而後在線程建立後啓動這些任務,若是線程數量超過了最大數量,那麼多餘的線程要排隊等候,等待其餘線程處理完畢,再從隊列中取出任務來執行。java

主要特色:線程複用 控制最大併發數 管理線程小程序

  • 下降資源消耗。經過重複用與建立的線程下降線程建立和銷燬形成的消耗
  • 提升響應速度。當任務到達時,任務能夠不須要等待線程建立這一過程
  • 提升線程的可管理性。線程是稀缺資源,若是無限制的建立,不只會消耗系統資源,還會下降系統的穩定性,使用線程池能夠進行統一的分配,調優和監控

線程池介紹

架構實現

java中的線程池是經過executor框架實現的,該框架中用到了Executor接口、Executors類、ThreadPoolExecutor類和ScheduledThreadPoolExecutor類。Executors和Executor的關係等同於從Collections與Collection和Arrays與Array,前者提供了許多輔助工具類能夠很方便的使用。 緩存

經常使用方法

線程池的有5種,其中最經常使用的有如下三種多線程

  • Executors.newFixedThreadPool(int) 固定線程個數:執行長期任務,性能好不少 具體實現:
public static ExecutorService newFixedThreadPool(int nThreads) {
       return new ThreadPoolExecutor(nThreads, nThreads,
                                     0L, TimeUnit.MILLISECONDS,
                                     new LinkedBlockingQueue<Runnable>());
   }
複製代碼

主要特色:架構

  1. 建立一個定長線程池,可控制線程最大併發數,超出的線程會在隊列中等待
  2. newFixedThreadPool建立的線程池corePoolSize和maximumPoolSize值相等,它使用的LinkedBolickingQueue
  • Executors.newSingleThreadExecutor() 只有一個線程 適用於單個任務現行執行的場景併發

    public static ExecutorService newSingleThreadExecutor() {
          return new FinalizableDelegatedExecutorService
              (new ThreadPoolExecutor(1, 1,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>()));
      }
    複製代碼

    主要特色:框架

    1. 建立一個單線程化的線程池,他只會用惟一的工做線程來執行任務,保證全部任務按照指定順序執行
    2. newSingleThreadExector將corePoolSize和maximumPoolSize都設置爲1,它使用的LinkedBlockingQueue
  • Executors.newCachedThreadPool() 動態擴大線程數,適用於執行不少短時間異步的小程序或負載比較輕的服務異步

    public static ExecutorService newCachedThreadPool() {
          return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                        60L, TimeUnit.SECONDS,
                                        new SynchronousQueue<Runnable>());
      }
    複製代碼

    主要特色:ide

    1. 建立一個可緩存線程池,若是線程池長度超過處理須要,可靈活回收空閒線程,若無可回收,則新建線程。
    2. newCachedThreadPool將corePoolSize設置爲0,將maximumPoolSize設置爲Integer.MAX_VALUE,使用的SynchronousQueue,也就是說來了任務就建立線程運行,當線程空閒60秒,就銷燬線程。

    以上三段代碼可知,線程池的構造都是從一個方法而來: ThreadPoolExecutor工具

ThreadPoolExector

由以上三段代碼可知,在Exectors內部建立線程池ide時候,實際建立的都是一個ThreadPoolExecutor對象,只是對ThreadPoolExecutor構造方法,進行了默認值的設定。其構造方法以下:

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.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

複製代碼

參數含義以下:

  • corePoolSize:常駐核心線程數
  • maximumPoolSize:線程池中可以容納同時執行的最大線程數,必須大於等於1
  • workQueue:任務隊列,被提交到那時還沒有被執行的任務
  • keepAliveTime:線程空閒時間長度,即非核心線程,當隊列中排隊的任務過多,會建立出來小於等於最大線程數的線程做爲臨時線程來執行隊列中的任務,若是這類臨時線程空閒時間超過keepAliveTime,則會被銷燬,只剩下核心線程數
  • unit 空閒時間的單位
  • threadFactory 線程工廠,用於建立線程通常用默認的便可
  • RejectedExecutionHandler 拒絕策略,表示當隊列滿了而且工做線程大於等於線程吃的最大線程數時,應對再接收到的線程的策略

參數使用場景:

  1. 建立線程池後,等待提交過來的任務請求
  2. 當調用execute()方法添加一個請求任務時,線程池會作以下判斷:
    1. 若是正在運行的線程數小於corePoolSize時,建立新的線程運行這個任務
    2. 若是正在運行的線程數大於等於corePoolSize,那麼把任務放入隊列
    3. 若是這時隊列滿了,且正在運行的線程數小於maximumPool,那麼還要建立非核心線程運行這個任務
    4. 若是線程數已經達到maximumPool,那麼線程池會啓動飽和拒絕策略來執行。
  3. 當一個線程執行任務完成,他會從隊列中取出下一個任務來執行
  4. 當一個線程空閒超過keepAliveTime,線程池會判斷 若是當前運行線程數大於corePoolSize,則線程被銷燬
  5. 線程池全部任務完成後,最終會收縮到corePoolSize()

線程拒絕策略

當隊列滿了並且線程數已經達到maximumPoolSize,接下來的線程會受到拒絕策略的管控 拒絕策略有四種:

  • AbortPolicy(默認):直接拋出RejectedExecutionException一場阻止系統正常運行
  • CallerRunsPolicy:"調用者運行"一種調節機制,該策略既不會拋棄任務,也不會拋出異常,而是將某些任務回退到調用者那裏,從而下降新任務的流量
  • DiscardOldestPolicy:拋棄隊列中等待最久的任務,而後把當前任務加入隊列中嘗試再次提交當前任務
  • DiscardPolicy:直接丟棄任務,不予任何處理也不拋出異常。若是容許任務丟失,這是最好的一種方案

線程池配置合理線程數

CPU密集型

該任務須要大量的運算,而且沒有阻塞,CPU一直全速運行,CPU密集任務只有在真正的多核CPU上纔可能經過多線程加速
CPU密集型任務配置儘量少的線程數量:CPU核數+1個線程的線程池

IO密集型

IO密集型任務線程並非一直在執行任務,則應配置儘量多的線程,如CPU核數*2

  • 某大廠設置策略:IO密集型時,大部分線程都阻塞,故須要多配置線程數: 公式:CPU核數/1-阻塞係數 阻塞係數:0.8-0.9
    好比8核CPU: 8/1-0.9 = 80 個線程數

最終建議

根據阿里巴巴編碼規約,不容許使用Executors去建立,而是經過ThreadPoolExecutor的方式建立,這樣讓寫的同窗更加明確線程池的運行規則,避免資源耗盡的風險。 executors各個方法的弊端:

  1. newFixedThreadPool和newSingleThreadExecutor:
    主要問題是容許的隊列長度爲Integer.MAX_VALUE,堆積的請求處理隊列可能會耗費很是大的內存,甚至OOM
  2. newCachedThreadPool和newScheduledThreadPool: 主要問題是線程數最大數是Integer.MAX_VALUE,可能會建立數量很是多的線程,甚至OOM
相關文章
相關標籤/搜索