簡介
本專欄結合筆者面試的一些高頻率題目,詳細分析其原理、場景、底層機制、以及實際場景等。為了節約篇幅,我們注重分析一些原理、較為底層的東西,對于一些概念性的知識,不多贅述。也希望通過這個專欄,讓大家在面試的過程中能夠靈活應對。早日收到滿意的offer
關于線程池的一些概念,如線程池是什麼、為什麼要使用線程池,不多贅述。
1、為什麼不推薦使用Executors
線程池的建立方式有很多種,這也是面試最常問的問題,歸根結底,面試官還是為了考察線程池的參數含義。不推薦使用 Executors的原因是預設參數不友好:
- FixedThreadPool和SingleThreadPool,隊列長度為Integer.MAX_VALUE
- CachedThreadPool和ScheduledThreadPool:線程數量為Integer.MAX_VALUE
2、線程池核心參數分析
- corePoolSize:核心線程數
- maximumPoolSize:最大線程數
- workQueue:任務隊列
- RejectedExecutionHandler 拒絕政策
- keepAliveTime:空閑時間
- timeUnit:時間機關
- 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三個參數
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接口,通過日志、持久化等方式存儲不能被正常處理的任務以便将來恢複線程 |