Categories
程式開發

怎麼才算掌握了JDK中的線程池


JDK並發包下面的線程池是面試中經常被考查的點,之前我寫過一篇ThreadPoolExecutor源碼分析“的文章。因為篇幅有限當時沒說面試中常見的考查點和哪些點是應該掌握。那篇文章著實有點長,更合適用電腦看,結合源碼看。今天,我來談談自己覺得ThreadPoolExecutor哪些點是應該掌握的,這些點應該掌握的點正是面試中經常被問的東西。現在拋出幾個問題,如果你都能答上來,可以不用往下面看啦。

ThreadPoolExecutor中常用參數有哪些,作用是什麼?任務提交後,ThreadPoolExecutor會按照什麼策略去創建線程用於執行提交任務? ThreadPoolExecutor有哪些狀態,狀態之間流轉是什麼樣子的? ThreadPoolExecutor中的線程哪個時間點被創建?是任務提交後嗎?可以在任務提交前創建嗎? ThreadPoolExecutor中創建的線程哪個時間被啟動? ThreadPoolExecutor竟然是線程池那麼他是如何做到重複利用線程的? ThreadPoolExecutor中創建的同一個線程同一時刻能執行多個任務嗎?如果不能是通過什麼機制保證ThreadPoolExecutor中的同一個線程只能執行完一個任務,才會機會去執行另一個任務? ThreadPoolExecutor中關閒線程池的方法shutdown與shutdownNow的區別是什麼?通過submit方法向ThreadPoolExecutor提交任務後,當所有的任務都執行完後不調用shutdown或shutdownNow方法會有問題嗎? ThreadPoolExecutor有沒有提供擴展點,方便在任務執行前或執行後做一些事情?

如果回答的上就pass吧,哈哈

ThreadPoolExecutor參數有哪些與創建線程策略?

ThreadPoolExecutor參數

corePoolSize 線程池中的核心線程數mmaximumPoolSize 線程池中的最大線程數keepAliveTime 當線程池中線程數量超過corePoolSize時,允許等待多長時間從workQueue中拿任務unit keepAliveTime 對應的時間單位,為TimeUnit類。 workQueue 阻塞隊列,當線程池中線程數超過corePoolSize時,用於存儲提交的任務。 threadFactory 線程池採用,該線程工廠創建線程池中的線程。 handler 為RejectedExecutionHandler,當線程線中線程超過maximumPoolSize時採用的,拒絕執行處理器。

創建線程策略

怎麼才算掌握了JDK中的線程池 9

簡單介紹一下,一個任務提交給線程池後,線程池創建線程來執行提交任務的流程。

1、當提交任務時線程池中的來用執行任務的線程數小於corePoolSize(核心線程數),則線程池利用ThreadFacory(線程工廠)創建線程用於執行提交的任務。否則執行第二2步。

2、當提交任務時線程池中的來用執行任務的線程數大於corePoolSize(核心線程數),但workQueue沒有滿,則線程池會將提交的任務先保存在workQueue(工作隊列),等待線程池中的線程執行完其它已提交任務後會循環從workQueue中取出任務執行。否則執行第3步。

3、當提交任務時線程池中的來用執行任務大於corePoolSize(核心線程數),且workQueu已滿,但沒有超過maximunPoolSize(最大線程數),則線程池利用ThreadFacory(線程工廠)創建線程用於執行提交的任務。否則執行4。

4、當提交任務時線程池中的來用執行任務大於maximunPoolSize,執行線程池中配置的拒絕策略(RejectedExecutionHanlder)。

所以在設置ThreadPoolExecutor的參數時一定要特別小心,不建議採用很大的ArrayBlockQueue或不限大小的LinkedBlockQueue,同時corePoolSize也不應該設置過大。 CUP密集的任務的話可以設置小一點(CUP數據+1這種)避免不必要的上下文切換;而對於IO密集的任務則corePoolSize則可以設置的大一點,可以避免長時間IO等待而CUP卻空閒。 threadFactory建議採用自己定義的,讓其創建的線程容易區分,方便問題定位。

線程池有哪些狀態,狀態之間流轉是什麼樣子的?

RUNNING:運行中,接收新的任務或處理隊列中的任務。 SHUTDOWN:關閉,不再接收新的任務,但會處理隊列中的任務值為0。 STOP:停止,不再接收新的任務,也不處理隊列中的任務,併中斷正在處理的任務。 TIDYING:所有任務已結束隊列大小為0,轉變TIDYING狀態的線程將會執行terminated()方法。 TERMINATED:結束terminated()已被執行完。

狀態流程如下圖:

怎麼才算掌握了JDK中的線程池 10

池程池中的線程哪個時間點被創建?

ThreadPoolExecutor中的線程哪個時間點被創建?是任務提交後嗎?可以在任務提交前創建嗎?

一般在任務被提交後,線程池會利用線程工廠去創建線程,但當線程池中線程數已為corePoolSize時或maxmumPoolSize時不會。可以在任務提交前通過prestartCoreThread方法或prestartAllCoreThreads方法預先創建核心線程。具體可以參考這下這個圖:

怎麼才算掌握了JDK中的線程池 11

ThreadPoolExecutor中創建的線程哪個時間被啟動?

線程池中線程實現是在addWorker方法中被創建的,詳見之前文章中addWorker方法分析。創建後完,該線程就被啟動。線程池中被創建的線程被封裝到了Worker對像中,而Worker類又實現了Runnable接口,線程池中的線程又引用了worker。當線程被start後實際就有機會等待操作系統調度執行Worker類的run方法。

Worker(Runnable firstTask) {
setState(-1);
this.firstTask = firstTask;
//创建的线程引用了worker
this.thread = getThreadFactory().newThread(this);
}
复制代码

ThreadPoolExecutor竟然是線程池那麼他是如何做到重複利用線程的?

一旦線程池通過ThreadFactory創建好線程後,就會將創建的線程封裝到了Worker對像中,同時啟動該線程。新創建的線程會執行剛提交的任務,同時會不斷地從workerQueue中取出任務執行。線程池的線程復用正是通過不斷地從workerQueue中取出任務來執行達到的。源碼分析見runWorkers方法分析。

ThreadPoolExecutor中創建的同一個線程同一時刻能執行多個任務嗎?

同時一時刻不能執行多個任務,只有一個任務執行完時才能去執行另一個任務。上面說到線程池中通過ThreadFacory創建的線程最後會被封裝到Worker中,而該線程又引用了Worker,start線程後,任務其實是在Worker中的run方法中被執行,最終run又將任務執行代理給ThreadPoolExecutor的runWorker方法。

private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
{...}
复制代码

Worder一方面實現了Runnable,另一方面又繼承了AQS。通過實現AQS,Worker具有了排它鎖的語義,每次在執行提交任務時都會先lock操作,執行完任務後再做unlock操作。正是這個加鎖與解鎖的操作,保證了同一個線程要執行完當前任務才有機再去執行另一個任務。

ThreadPoolExecutor中關閒線程池的方法shutdown與shutdownNow的區別是什麼?

shutdown方法是將線程池的狀態設置為SHUTDOWN,此時新任務不能被提交(提交會拋出異常),workerQueue的任務會被繼續執行,同時線程池會向那些空閒的線程發出中斷信號。空閒的線程實際就不沒在執行任務的線程。如何被封裝在worker裡的線程能加鎖,這裡這個線程實現會就空閒的。下面是向空閒的線程發出中斷信號源碼。

private void interruptIdleWorkers(boolean onlyOne) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers) {
Thread t = w.thread;
//w.tryLock()用于加锁,看线程是否在执行任务
if (!t.isInterrupted() && w.tryLock()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
} finally {
w.unlock();
}
}
if (onlyOne)
break;
}
} finally {
mainLock.unlock();
}
}
复制代码

shutdownNow方法是將線程池的狀態設置為STOP,此時新任務不能被提交(提交會拋出異常),線程池中所有線程都會收到中斷的信號。具體線程會作出什麼響應,要看情況,如果線程因為調用了Object的wait、join方法或是自身的sleep方法而阻塞,那麼中斷狀態會被清除,同時拋出InterruptedException。其它情況可以參考Thread.interrupt方法的說明。 shutdownNow方法向所有線程發出中斷信息源碼如下:

private void interruptWorkers() {
final ReentrantLock mainLock = this.mainLock;
//加锁操作保证中断过程中不会新woker被创建
mainLock.lock();
try {
for (Worker w : workers)
w.interruptIfStarted();
} finally {
mainLock.unlock();
}
}
复制代码

通過submit方法向ThreadPoolExecutor提交任務後,當所有的任務都執行完後不調用shutdown或shutdownNow方法會有問題嗎?

如果沒指核心線程允許超時將會有問題。核心線程允許超時是指在從wokerQueue中獲取任務時,採用的阻塞的獲取方式等待任務到來,還是通過設置超時的方式從同步阻塞隊列中獲取任務。即是通通過BlockingQueue的poll方法獲取任務還是take方法獲取任務。可參考之前的源碼分析中的getTask方法分析。如果不調用shutdown或shutdownNow方法,核心線程由於在getTask方法調用BlockingQueue.take方法獲取任務而處於一直被阻塞掛起狀態。核心線程將永遠處於Blocking的狀態,導致內存洩漏,主線程也無法退出,除非強制kill。試著運行如下程序會發現,程序無法退出。

public class Test {
public static void main(String args[]) {
ExecutorService executorService = new ThreadPoolExecutor(3, 3,10L, TimeUnit.SECONDS, new ArrayBlockingQueue(10));
executorService.submit(new Runnable() {
@Override
public void run() {
System.out.println("thread name " + Thread.currentThread().getName());
}
});
}
}
复制代码

所在使用線程池時一定要記得根本具體場景調用shutdown或shutdownNow方法關閉線程池。 shutdown方法適用於提交任務都要被執行完的場景,shutdownNow方法適用於不關心提交任務是否執行完的場景。

ThreadPoolExecutor有沒有提供擴展點,方便在任務執行前或執行後做一些事情?

線程池提供了三個擴展點,分別是提交任務的run方法或是call方法被調用前與被調後,即beforeExecutor與afaterExecutor方法;另外一個擴展點是線程池的狀態從TIDYING狀態流轉為TERMINATED狀態時terminated方法會被調用。

總結

本來只是想寫一點點,寫著寫著就發現又有點長。這篇主要是介紹了ThreadPoolExecutor中個人認為比較重要點,同時也是把ThreadPoolExecutor再梳理一下發現自己之前理解有偏差的地方。

看完三件事❤️

如果你覺得這篇內容對你還蠻有幫助,我想邀請你幫我三個小忙:

點贊,轉發,有你們的『點贊和評論』,才是我創造的動力。關注公眾號『 java爛豬皮』,不定期分享原創知識。同時可以期待後續文章ing🚀

怎麼才算掌握了JDK中的線程池 12

作者:葉易

出處:club.perfma.com/article/191…