天天看點

高頻面試Java之線程池

簡介

本專欄結合筆者面試的一些高頻率題目,詳細分析其原理、場景、底層機制、以及實際場景等。為了節約篇幅,我們注重分析一些原理、較為底層的東西,對于一些概念性的知識,不多贅述。也希望通過這個專欄,讓大家在面試的過程中能夠靈活應對。早日收到滿意的offer

關于線程池的一些概念,如線程池是什麼、為什麼要使用線程池,不多贅述。

1、為什麼不推薦使用Executors

線程池的建立方式有很多種,這也是面試最常問的問題,歸根結底,面試官還是為了考察線程池的參數含義。不推薦使用 Executors的原因是預設參數不友好:

  1. FixedThreadPool和SingleThreadPool,隊列長度為Integer.MAX_VALUE
  2. CachedThreadPool和ScheduledThreadPool:線程數量為Integer.MAX_VALUE
2、線程池核心參數分析
  1. corePoolSize:核心線程數
  2. maximumPoolSize:最大線程數
  3. workQueue:任務隊列
  4. RejectedExecutionHandler 拒絕政策
  5. keepAliveTime:空閑時間
  6. timeUnit:時間機關
  7. threadFactory:線程工廠

針對參數會問到的是任務隊列和拒絕政策,考察對java集合、拒絕政策的處理。

隊列名稱 簡單分析
ArrayBlockingQueue 數組,ReentrantLock+Condition,生産者、消費者共用一把鎖
LinkedBlockingQueue 連結清單,ReentrantLock+Condition,生産者、消費者的鎖各自獨立,吞吐量高于ArrayBlockingQueue
SynchronousQueue 不存儲元素,插入操作必須等到另一個線程調用移除操作,吞吐量高于LinkedBlockingQueue
PriorityBlockingQueue 帶優先級

JDK自帶政策有四種,但是對任務的處理都不友好,我們需要自定義拒絕政策,實作RejectedExecutionHandler即可

政策名 對任務的處理方式
Abort 抛出異常
CallerRuns 使用調用者所線上程來運作任務
DiscardOldest 丢棄隊列裡最近的一個任務,并執行目前任務
Discard 直接丢棄掉
3、線程池生命周期

生命周期一般考察的不多,但是需要了解一下。

生命周期 對應操作
running 接收新任務,處理隊列中的任務
shutdown 不接收新任務,但是會處理隊列中的任務
stop 不接收新任務,不處理隊列中的任務,并且中斷正在執行的任務
tidying 清空worker對象
terminated 完全停止
4、線程池膨脹過程

該問題是最常考察的。主要針對 corePoolSize、maximumPoolSize、workQueue三個參數

高頻面試Java之線程池
5、線程池執行過程

線程池執行任務的流程如下,可結合源碼分析:

  • 1、根據任務送出和執行情況,逐漸飽和線程池。若線程數小于corePoolSize,建立線程;若任務隊列未滿,則将任務加入到隊列中;若線程數小于maximumPoolSize,則建立線程;若線程池飽和,則根據回絕政策回絕新送出的線程
  • 2、調用addWorker方法,将任務包裝成Worker對象。Worker類繼承了線程Thread類,線程啟動後,通過runWorker方法執行任務
  • 3、調用runWorker方法,先判斷addWorker方法是否傳遞任務,若有,則執行任務;若無,則通過getTask()方法從隊列中擷取任務
  • 4、getTask方法,會判斷目前線程數是否大于corePoolSize或者allowCoreThreadTimeOut是否為true,以決定是是以阻塞的形式還是以帶逾時時間的形式去擷取任務,并決定是否回收線程
  • 5、如果線程應該被回收,則調用processWorkerExit方法回收線程,回收的時候要注意判斷若線程池的狀态為running或shutdown,并且任務隊列不為空,則至少需要保留一個線程。但是由于回收線程在前,則需要再次通過addWorker方法建立一個線程,以用來執行隊列中的任務
6、線程池大小預估

這個是比較常見的問題。包含線程數、隊列大小兩個方面

6.1 公式一

CPU 密集型應用,線程池大小設定為 N + 1;IO 密集型應用,線程池大小設定為 2N。這種估算方式網上比較常見,但是實際應用中很難界定到底什麼是CPU 密集型應用、什麼是IO 密集型應用。

6.2 公式二

core = tps(每秒能完成的事務數 = 并發量 / 平均響應時間)* time,max = tps * time * (1.7 ~ 2),該估算方式考慮到了業務場景,但是業務流量往往是不均勻分布的

6.3 公式三

線程數 = (1 + 線程等待時間/線程CPU時間 * 目标CPU的使用率 * 處理器核心數。例如:CPU Time 0.5ms,Wait Time 1.5ms,CPU使用率是90%,CPU核心數為8,(1 + 1.5/0.5) * 90% * 8 = 28.8。可以結合ThreadMXBean,計算出線程CPU時間、線程等待時間。

6.4 公式四

隊列大小:queueSize = (coreSize/taskCost)*resTime。假設每個任務執行耗時,100ms,系統能夠忍受的最大響應時間為2秒,線程池大小為32,那麼隊列大小可以設定為640

7、自定義線程池

實際應用中,可以自定義線程池,動态控制線程池的各個參數值、監控線程池的狀态

自定義選項 分析
線程數 通過setCorePoolSize、setMaximumPoolSize動态設定線程數
隊列 自定義隊列以支援長度的動态調整,拷貝LinkedBlockingQueue代碼,提供setQueueSize方法
監控 通過beforeExecute、afterExecute方法,監控線程最長執行時間、平均執行時長、線程活躍度、隊列長度等,并提供預警
拒絕政策 實作RejectedExecutionHandler接口,通過日志、持久化等方式存儲不能被正常處理的任務以便将來恢複線程