JUC知識點總結(八)線程池面試知識點總結

15. 線程池

15.1 爲什麼要使用線程池?

  • 降低資源消耗:通過重複利用已創建的線程降低線程創建和銷燬造成的消耗

  • 提高響應速度:任務到達時,任務可以不需要等到線程創建就能立即執行

  • 提高線程的可管理性

15.2 線程池的體系結構:

java.util.concurrent.Executor : 負責線程的使用與調度的根接口

  • ExecutorService 子接口: 線程池的主要接口
    • ThreadPoolExecutor 線程池的實現類
    • ScheduledExecutorService 子接口:負責線程的調度
      • ScheduledThreadPoolExecutor :繼承 ThreadPoolExecutor,實現 ScheduledExecutorService

15.3 創建線程池的方法

private static ExecutorService executor = new ThreadPoolExecutor(10, 10,
        60L, TimeUnit.SECONDS, new ArrayBlockingQueue(10));
/***********************************************************************/
public ThreadPoolExecutor(int corePoolSize, //核心池大小大小
                              int maximumPoolSize, //最大容量
                              long keepAliveTime, //線程數大於corePoolSize後,空閒存活時間
                              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;
}   
15.3.1 線程池的拒絕策略

等待隊列也已經滿了,再也塞不下新任務了。同時線程池中的max線程數也達到了,無法繼續爲新任務服務。這時候我們就需要拒絕策略機制合理的解決這個問題。

  • AbortPolicy 默認 拋出RejectedExecutionException異常阻止系統正常運行

  • CallerRunsPolicy 該策略既不會拋出任務,也不會拋出異常,而是將某些任務交由調用者完成。

  • DiscardOldestPolicy 拋棄隊列中等待最久的任務,然後把當前任務加入到隊列中嘗試再次提交當前任務。

  • DiscardPolicy 直接丟棄任務,不予任何處理也不拋出異常。如果允許任務丟失,這是最好的一種方案。

15.3.2 線程池的工作隊列
  • ArrayBlockingQueue (有界隊列)

  • LinkedBlockingQueue (無界隊列)可以指定對立的大小,也可以不指定,默認類似一個無限大小的容量(Integer.MAX_VALUE)

  • SynchronousQueue(同步隊列) 不存儲元素,每個插入操作必須等到另一個線程調用移除操作,否則插入操作一直處於阻塞狀態, 吞吐量通常要高於LinkedBlockingQueue

  • DelayQueue(延遲隊列) 一個任務定時週期的延遲執行的隊列。根據指定的執行時間從小到大排序,否則根據插入到隊列的先後排序

  • PriorityBlockingQueue(優先級隊列)

有界隊列即長度有限,滿了以後ArrayBlockingQueue會插入阻塞。無界隊列就是裏面能放無數的東西而不會因爲隊列長度限制被阻塞,但是可能會出現OOM異常。

15.4 線程池的提交與關閉方法

  • threadPool.execute(Runnable task) 提交無返回值的方法
  • threadPool.submit(Callable task) 提交有返回值的方法,返回一個future對象
  • threadPool.shutdown() 等待任務執行完關閉
  • threadPool.shutdownNow() 立即關閉

15.5 線程池的底層工作原理

  • 在創建線程池後,等待提交過來的任務請求。

  • 調用execute()方法提交一個新任務到線程池,處理流程:

    • 判斷核心線程池裏的線程是否都在執行任務。如果不是,則創建一個新的工作線程執行任務。
    • 判斷工作隊列是否已滿,如果工作隊列沒有滿,則將新提交的任務存儲在這個工作隊列裏。
    • 判斷線程池的線程是否都處於工作狀態。如果沒有,則創建新的工作線程來執行任務。如果滿了,則交給飽和策略來處理這個任務。
  • 當一個線程完成任務時,它會從隊列中取下一個任務來執行。

  • 當一個線程無事可做超過一定的時間(keepAliveTime)時,線程池會判斷:

  • 如果當前運行的線程數大於corePoolSize,那麼這個線程就被停掉。

  • 所以當線程池的所有任務完成後,它最終會收縮到corePoolSize的大小。

以ThreadPoolExecutor執行execute方法舉例,分爲4種情況:

  • 如果當前運行線程數少於corePoolSize,則創建新線程來執行任務

  • 如果運行的線程等於或多餘corePoolSize,則將任務加入BlockingQueue

  • 如果BlockingQueue已滿,則創建新的線程來處理任務

  • 如果創建新線程將使當前運行的線程超出maximumPoolSize,任務將被拒絕,並調用對應的策略

工作線程:線程池創建線程時,會將線程封裝成工作線程Worker,Worker在執行完任務後,還會循環獲取工作隊列裏 的任務來執行。

15.6 使用線程池的風險

  • 死鎖:線程池引入了另一種死鎖可能,所有池程都在執行已阻塞的等待隊列中另一任務的執行結果的任務,但這一任務卻因爲沒有未被佔用的線程而不能運行。
  • 資源不足
  • 併發錯誤
  • 線程泄漏:當從池中一個線程執行任務後該線程卻沒有返回池時,會發生這種情況。
  • 請求過載

15.7 創建線程池的工具類 : Executors

線程池種類 特點
newFixedThreadPool() 創建固定大小的線程池,核心線程數和最大線程數大小一樣,keepAliveTime爲0,阻塞隊列是LinkedBlockingQueue,處理CPU密集型的任務。
newCachedThreadPool() 核心線程數爲0,最大線程數爲Integer.MAX_VALUE,keepAliveTime爲60s,阻塞隊列是SynchronousQueue,併發執行大量短期的小任務。
newSingleThreadExecutor() 創建單個線程池。核心線程數和最大線程數大小一樣且都是1,keepAliveTime爲0,阻塞隊列是LinkedBlockingQueue,按添加順序串行執行任務。
newScheduledThreadPool() 創建固定大小的線程,最大線程數爲Integer.MAX_VALU,阻塞隊列是DelayedWorkQueue

注意:

  • FixedThreadPool 和 SingleThreadPool允許的請求隊列(底層實現是LinkedBlockingQueue)長度爲Integer.MAX_VALUE,可能會堆積大量的請求,從而導致OOM
  • CachedThreadPool 和 ScheduledThreadPool 允許的創建線程數量爲Integer.MAX_VALUE,可能會創建大量的線程,從而導致OOM。

下一篇
JUC知識點總結(九)Fork/Join框架解析

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章