Java線程池面試必備:核心參數(shù)、工作流、監(jiān)控、調優(yōu)手段

① 合理使用線程池的好處
Java的線程池是運用場景最多的并發(fā)框架,幾乎所有需要異步或者并發(fā)執(zhí)行任務的程序都可以使用線程池。 合理使用線程池能帶來的好處:
降低資源消耗。 通過 重復利用已經創(chuàng)建的線程降低線程創(chuàng)建的和銷毀造成的消耗。例如,工作線程Woker會無線循環(huán)獲取阻塞隊列中的任務來執(zhí)行。提高響應速度。 當任務到達時, 任務可以不需要等到線程創(chuàng)建就能立即執(zhí)行。提高線程的可管理性。 線程是稀缺資源,Java的線程池可以對線程資源進行 統(tǒng)一分配、調優(yōu)和監(jiān)控。
② 線程池的工作流程
一個新的任務到線程池時,線程池的處理流程如下:1. 線程池判斷核心線程池里的線程是否都在執(zhí)行任務。如果不是,創(chuàng)建一個新的工作線程來執(zhí)行任務。如果核心線程池里的線程都在執(zhí)行任務,則進入下個流程。
線程池判斷阻塞隊列是否已滿。如果阻塞隊列沒有滿,則將新提交的任務存儲在阻塞隊列中。如果阻塞隊列已滿,則進入下個流程。線程池判斷線程池里的線程是否都處于工作狀態(tài)。如果沒有,則創(chuàng)建一個新的工作線程來執(zhí)行任務。如果已滿,則交給飽和策略來處理這個任務。
線程池的核心實現(xiàn)類是 ThreadPoolExecutor類,用來執(zhí)行提交的任務。因此,任務提交到線程池時,具體的處理流程是由ThreadPoolExecutor類的execute()方法去完成的。
如果當前運行的線程少于 corePoolSize,則創(chuàng)建新的工作線程來執(zhí)行任務(執(zhí)行這一步驟需要獲取全局鎖)。如果當前運行的線程大于或等于 corePoolSize,而且BlockingQueue未滿,則將任務加入到BlockingQueue中。如果 BlockingQueue已滿,而且當前運行的線程小于maximumPoolSize,則創(chuàng)建新的工作線程來執(zhí)行任務(執(zhí)行這一步驟需要獲取全局鎖)。如果當前運行的線程大于或等于 maximumPoolSize,任務將被拒絕,并調用RejectExecutionHandler.rejectExecution()方法。即調用飽和策略對任務進行處理。
工作線程(Worker): 線程池在創(chuàng)建線程時,會將線程封裝成工作線程Woker。Woker在執(zhí)行完任務后,不是立即銷毀而是 循環(huán)獲取阻塞隊列里的任務來執(zhí)行。
③ 線程池的創(chuàng)建(7個參數(shù))
可以通過 ThreadPoolExecutor來創(chuàng)建一個線程池:
new ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler)
corePoolSize(線程池的基本大?。?/strong>
提交一個任務到線程池時,線程池會創(chuàng)建一個新的線程來執(zhí)行任務。注意: 即使有 空閑的基本線程能執(zhí)行該任務,也會創(chuàng)建新的線程。如果線程池中的線程數(shù)已經大于或等于 corePoolSize,則不會創(chuàng)建新的線程。如果調用了線程池的 prestartAllCoreThreads()方法,線程池會提前創(chuàng)建并啟動所有基本線程。
maximumPoolSize(線程池的最大數(shù)量): 線程池允許創(chuàng)建的最大線程數(shù)。
阻塞隊列已滿,線程數(shù)小于 maximumPoolSize便可以創(chuàng)建新的線程執(zhí)行任務。如果使用無界的阻塞隊列, 該參數(shù)沒有什么效果。
workQueue(工作隊列): 用于保存等待執(zhí)行的任務的阻塞隊列。
ArrayBlockingQueue:基于數(shù)組結構的有界阻塞隊列,按FIFO(先進先出)原則對任務進行排序。使用該隊列,線程池中能創(chuàng)建的最大線程數(shù)為maximumPoolSize。LinkedBlockingQueue:基于鏈表結構的無界阻塞隊列,按FIFO(先進先出)原則對任務進行排序,吞吐量高于ArrayBlockingQueue。使用該隊列,線程池中能創(chuàng)建的最大線程數(shù)為corePoolSize。靜態(tài)工廠方法Executor.newFixedThreadPool()使用了這個隊列。SynchronousQueue:一個不存儲元素的阻塞隊列。添加任務的操作必須等到另一個線程的移除操作,否則添加操作一直處于阻塞狀態(tài)。靜態(tài)工廠方法Executor.newCachedThreadPool()使用了這個隊列。PriorityBlokingQueue:一個支持優(yōu)先級的無界阻塞隊列。使用該隊列,線程池中能創(chuàng)建的最大線程數(shù)為corePoolSize。
keepAliveTime(線程活動保持時間): 線程池的 工作線程空閑后,保持存活的時間。如果任務多而且任務的執(zhí)行時間比較短,可以調大keepAliveTime,提高線程的利用率。unit(線程活動保持時間的單位): 可選單位有 DAYS、HOURS、MINUTES、毫秒、微秒、納秒。handler(飽和策略,或者又稱拒絕策略): 當隊列和線程池都滿了,即線程池飽和了,必須采取一種策略處理提交的新任務。
AbortPolicy:無法處理新任務時,直接拋出異常,這是默認策略。CallerRunsPolicy:用調用者所在的線程來執(zhí)行任務。DiscardOldestPolicy:丟棄阻塞隊列中最靠前的一個任務,并執(zhí)行當前任務。DiscardPolicy:直接丟棄任務。
threadFactory: 構建線程的工廠類 總結:
常用的5個,核心池、最大池、空閑時間、時間的單位、阻塞隊列;另外兩個:拒絕策略、線程工廠類 常見線程池的創(chuàng)建參數(shù)如下。PS: CachedThreadPool核心池為0,最大池為Integer.MAX_VALUE,相當于只使用了最大池;其他線程池,核心池與最大池一樣大,因此相當于只用了核心池。
FixedThredPool: new ThreadExcutor(n, n, 0L, ms, new LinkedBlockingQueue<Runable>()
SingleThreadExecutor: new ThreadExcutor(1, 1, 0L, ms, new LinkedBlockingQueue<Runable>())
CachedTheadPool: new ThreadExcutor(0, max_valuem, 60L, s, new SynchronousQueue<Runnable>());
ScheduledThreadPoolExcutor: ScheduledThreadPool, SingleThreadScheduledExecutor.
如果使用的阻塞隊列為無界隊列,則永遠不會調用拒絕策略,因為再多的任務都可以放在隊列中。 SynchronousQueue是不存儲任務的,新的任務要么立即被已有線程執(zhí)行,要么創(chuàng)建新的線程執(zhí)行。
④ 向線程池提交任務
使用 ThreadPoolEXecutor.execute()方法來提交任務:
public void execute(Runnable command) {
// command為null,拋出NullPointerException
if (command == null)
throw new NullPointerException();
int c = ctl.get();
// 線程池中的線程數(shù)小于corePoolSize,創(chuàng)建新的線程
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))// 創(chuàng)建工作線程
return;
c = ctl.get();
}
// 將任務添加到阻塞隊列,如果
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}// 阻塞隊列已滿,嘗試創(chuàng)建新的線程,如果超過maximumPoolSize,執(zhí)行handler.rejectExecution()
else if (!addWorker(command, false))
reject(command);
}
⑤ 線程池的五種運行狀態(tài)
RUNNING : 該狀態(tài)的線程池既能接受新提交的任務,又能處理阻塞隊列中任務。 SHUTDOWN: 該狀態(tài)的線程池不能接收新提交的任務,但是能處理阻塞隊列中的任務。(政府服務大廳不在允許群眾拿號了,處理完手頭的和排隊的政務就下班。)
處于 RUNNING 狀態(tài)時,調用shutdown()方法會使線程池進入到該狀態(tài)。注意: finalize() 方法在執(zhí)行過程中也會隱式調用shutdown()方法。
STOP: 該狀態(tài)的線程池不接受新提交的任務,也不處理在阻塞隊列中的任務,還會中斷正在執(zhí)行的任務。(政府服務大廳不再進行服務了,拿號、排隊、以及手頭工作都停止了。)
在線程池處于 RUNNING 或 SHUTDOWN 狀態(tài)時,調用shutdownNow() 方法會使線程池進入到該狀態(tài);
TIDYING: 如果 所有的任務都已終止,workerCount (有效線程數(shù))=0。
線程池進入該狀態(tài)后會調用 terminated() 鉤子方法進入TERMINATED 狀態(tài)。
TERMINATED: 在 terminated()鉤子方法執(zhí)行完后進入該狀態(tài),默認terminated()鉤子方法中什么也沒有做。
⑥ 線程池的關閉(shutdown或者shutdownNow方法)
可以通過調用線程池的 shutdown或者shutdownNow方法來關閉線程池:遍歷線程池中工作線程,逐個調用interrupt方法來中斷線程。shutdown方法與shutdownNow的特點:
shutdown方法將線程池的狀態(tài)設置為SHUTDOWN狀態(tài),只會中斷空閑的工作線程。shutdownNow方法將線程池的狀態(tài)設置為STOP狀態(tài),會中斷所有工作線程,不管工作線程是否空閑。調用兩者中任何一種方法,都會使 isShutdown方法的返回值為true;線程池中所有的任務都關閉后,isTerminated方法的返回值為true。通常使用 shutdown方法關閉線程池,如果不要求任務一定要執(zhí)行完,則可以調用shutdownNow方法。
2. java線程池的調優(yōu)以及監(jiān)控
① 線程池的調優(yōu)(線程池的合理配置)
先從以下幾個角度分析任務的特性:
任務的性質: CPU 密集型任務、IO 密集型任務和混合型任務。任務的優(yōu)先級: 高、中、低。 任務的執(zhí)行時間: 長、中、短。 任務的依賴性: 是否依賴其他系統(tǒng)資源,如數(shù)據(jù)庫連接。
任務性質不同的任務可以用不同規(guī)模的線程池分開處理。 可以通過 Runtime.getRuntime().availableProcessors()方法獲得當前設備的 CPU 個數(shù)。
CPU 密集型任務配置 盡可能小的線程,如配置 N c p u + 1 N_{cpu}+1Ncp**u+1 個線程的線程池。IO 密集型任務則由于線程并不是一直在執(zhí)行任務,則 配置盡可能多的線程,如2 ? N c p u 2N_{cpu}2?Ncp**u*。混合型任務,如果可以拆分,則 將其拆分成一個 CPU 密集型任務和一個 IO 密集型任務。只要這兩個任務執(zhí)行的時間相差不是太大,那么分解后執(zhí)行的吞吐率要高于串行執(zhí)行的吞吐率;如果這兩個任務執(zhí)行時間相差太大,則沒必要進行分解。
優(yōu)先級不同的任務可以使用優(yōu)先級隊列 PriorityBlockingQueue來處理,它可以讓優(yōu)先級高的任務先得到執(zhí)行。但是,如果一直有高優(yōu)先級的任務加入到阻塞隊列中,那么低優(yōu)先級的任務可能永遠不能執(zhí)行。執(zhí)行時間不同的任務可以交給 不同規(guī)模的線程池來處理,或者也可以使用優(yōu)先級隊列,讓執(zhí)行時間短的任務先執(zhí)行。依賴數(shù)據(jù)庫連接池的任務,因為線程提交 SQL 后需要等待數(shù)據(jù)庫返回結果, 線程數(shù)應該設置得較大,這樣才能更好的利用 CPU。建議使用有界隊列,有界隊列能 增加系統(tǒng)的穩(wěn)定性和預警能力??梢愿鶕?jù)需要設大一點,比如幾千。使用無界隊列,線程池的隊列就會越來越大,有可能會撐滿內存,導致整個系統(tǒng)不可用。
② 線程池的監(jiān)控
可以通過線程池提供的參數(shù)讀線程池進行監(jiān)控,有以下屬性可以使用:
taskCount:線程池需要執(zhí)行的任務數(shù)量,包括已經執(zhí)行完的、未執(zhí)行的和正在執(zhí)行的。completedTaskCount:線程池在運行過程中已完成的任務數(shù)量,completedTaskCount <= taskCount。largestPoolSize:線程池曾經創(chuàng)建過的最大線程數(shù)量,通過這個數(shù)據(jù)可以知道線程池是否滿過。如等于線程池的最大大小,則表示線程池曾經滿了。getPoolSize:線程池的線程數(shù)量。如果線程池不銷毀的話,池里的線程不會自動銷毀,所以線程池的線程數(shù)量只增不減。getActiveCount:獲取活動的線程數(shù)。
通過繼承線程池并重寫線程池的 beforeExecute,afterExecute和terminated方法,我們可以在任務執(zhí)行前,執(zhí)行后和線程池關閉前干一些事情。如監(jiān)控任務的 平均執(zhí)行時間,最大執(zhí)行時間和最小執(zhí)行時間等。這幾個方法在線程池里是空方法,如:
protected void beforeExecute(Thread t, Runnable r) { }
3. Java線程池的常見問題
1. 講講Java的線程池
基礎講解:
以 ThreadPoolExecutor為切入點,講解excute()方法中所體現(xiàn)的Java線程池運行流程。工作線程Worker,它的循環(huán)工作特點 如何新建線程池:7個參數(shù)(重點在阻塞隊列和飽和策略)
進階:
線程池 五個狀態(tài)的特點以及如何進行狀態(tài)之間的切換:running、shutdown、stop、tidying、terminated。如何關閉線程: shutdown方法和shutdownNow方法的特點線程池的調優(yōu)( 針對任務的不同特性+建議使用有界隊列)線程池的 監(jiān)控參數(shù)以及可以重寫的方法。
兩種主要的線程池類型:普通的線程池 ThreadPoolExecutor,支持延遲或周期性執(zhí)行的任務的線程池ScheduledThreadPoolExcutor。講解 ThreadPoolExcutor中5個常用參數(shù)+2個不常用參數(shù),包含的三種線程池:創(chuàng)建時的參數(shù)、運行的流程、各自適合的場景。講解 ScheduledThreadPoolExecutor的阻塞隊列的原理、如何更改任務的time。提供了五種定義好的線程池,都可以通過 Executors工具類去調用,比如Executors.newFixedThreadPool(12)
2. 具體的場景,如果corePoolSize為x,maximumPoolSize為y,阻塞隊列為z,第w個任務進來如何分配?
3. 線程池如何進行調優(yōu)?
線程池的調優(yōu)( 針對任務的不同特性+建議使用有界隊列)
4. 線程池中的核心參數(shù),超過核心size怎么處理,隊列滿怎么處理,拒絕策略有哪些?(比較具體)
1. 最通俗易懂的 volatile 關鍵字詳解,看完不懂你打我!
3. SpringBoot 操作 Redis的各種實現(xiàn)(以及Jedis、Redisson、Lettuce的區(qū)別比較)
最近面試BAT,整理一份面試資料《Java面試BATJ通關手冊》,覆蓋了Java核心技術、JVM、Java并發(fā)、SSM、微服務、數(shù)據(jù)庫、數(shù)據(jù)結構等等。
獲取方式:點“在看”,關注公眾號并回復 Java 領取,更多內容陸續(xù)奉上。
文章有幫助的話,在看,轉發(fā)吧。
謝謝支持喲 (*^__^*)
評論
圖片
表情

