怎麼才算掌握了JDK中的執行緒池

葉易發表於2019-01-10

怎麼才算掌握了JDK中的執行緒池

JDK併發包下面的執行緒池是面試中經常被考查的點,之前我寫過一篇ThreadPoolExecutor原始碼分析的文章。因為篇幅有限當時沒說面試中常見的考查點和哪些點是應該掌握。那篇文章著實有點長,更合適用電腦看,結合原始碼看。今天,我來談談自己覺得ThreadPoolExecutor哪些點是應該掌握的,這些點應該掌握的點正是面試中經常被問的東西。現在丟擲幾個問題,如果你都能答上來,可以不用往下面看啦。
1、ThreadPoolExecutor中常用引數有哪些,作用是什麼?任務提交後,ThreadPoolExecutor會按照什麼策略去建立執行緒用於執行提交任務?
2、ThreadPoolExecutor有哪些狀態,狀態之間流轉是什麼樣子的?
3、ThreadPoolExecutor中的執行緒哪個時間點被建立?是任務提交後嗎?可以在任務提交前建立嗎?
4、ThreadPoolExecutor中建立的執行緒哪個時間被啟動?
5、ThreadPoolExecutor竟然是執行緒池那麼他是如何做到重複利用執行緒的?
6、ThreadPoolExecutor中建立的同一個執行緒同一時刻能執行多個任務嗎?如果不能是通過什麼機制保證ThreadPoolExecutor中的同一個執行緒只能執行完一個任務,才會機會去執行另一個任務?
7、ThreadPoolExecutor中關閒執行緒池的方法shutdown與shutdownNow的區別是什麼?
8、通過submit方法向ThreadPoolExecutor提交任務後,當所有的任務都執行完後不呼叫shutdown或shutdownNow方法會有問題嗎?
9、ThreadPoolExecutor有沒有提供擴充套件點,方便在任務執行前或執行後做一些事情?
如果回答的上就pass吧,哈哈

1、ThreadPoolExecutor引數有哪些與建立執行緒策略?

ThreadPoolExecutor引數

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

建立執行緒策略WX20190110-083109@2x.png

簡單介紹一下,一個任務提交給執行緒池後,執行緒池建立執行緒來執行提交任務的流程。
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建議採用自己定義的,讓其建立的執行緒容易區分,方便問題定位。

2、執行緒池有哪些狀態,狀態之間流轉是什麼樣子的?

1、RUNNING:執行中,接收新的任務或處理佇列中的任務。
2、SHUTDOWN:關閉,不再接收新的任務,但會處理佇列中的任務值為0。
3、STOP:停止,不再接收新的任務,也不處理佇列中的任務,並中斷正在處理的任務。
4、TIDYING:所有任務已結束佇列大小為0,轉變TIDYING狀態的執行緒將會執行terminated()方法。
5、TERMINATED:結束terminated()已被執行完。
狀態流程如下圖:

怎麼才算掌握了JDK中的執行緒池
image

3、池程池中的執行緒哪個時間點被建立?

ThreadPoolExecutor中的執行緒哪個時間點被建立?是任務提交後嗎?可以在任務提交前建立嗎?
一般在任務被提交後,執行緒池會利用執行緒工廠去建立執行緒,但當執行緒池中執行緒數已為corePoolSize時或maxmumPoolSize時不會。可以在任務提交前通過prestartCoreThread方法或prestartAllCoreThreads方法預先建立核心執行緒。具體可以參考這下這個圖:


怎麼才算掌握了JDK中的執行緒池
image

在我之前的文章中有分析。

4、ThreadPoolExecutor中建立的執行緒哪個時間被啟動?

執行緒池中執行緒實現是在addWorker方法中被建立的,詳見之前文章中addWorker方法分析。建立後完,該執行緒就被啟動。執行緒池中被建立的執行緒被封裝到了Worker物件中,而Worker類又實現了Runnable介面,執行緒池中的執行緒又引用了worker。當執行緒被start後實際就有機會等待作業系統排程執行Worker類的run方法。

Worker(Runnable firstTask) { 
 setState(-1);  
 this.firstTask = firstTask;
 //建立的執行緒引用了
worker  this.thread = getThreadFactory().newThread(this);
}

複製程式碼

5、ThreadPoolExecutor竟然是執行緒池那麼他是如何做到重複利用執行緒的?

一旦執行緒池通過ThreadFactory建立好執行緒後,就會將建立的執行緒封裝到了Worker物件中,同時啟動該執行緒。新建立的執行緒會執行剛提交的任務,同時會不斷地從workerQueue中取出任務執行。執行緒池的執行緒複用正是通過不斷地從workerQueue中取出任務來執行達到的。原始碼分析見runWorkers方法分析

6、ThreadPoolExecutor中建立的同一個執行緒同一時刻能執行多個任務嗎?

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

private final class Worker  extends AbstractQueuedSynchronizerimplements Runnable   {
...
}

複製程式碼

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

7、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();  
    }
}
複製程式碼

8、通過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<Runnable>(10));       
 executorService.submit(new Runnable() {            
@Override            
public void run() {
System.out.println("thread name " + Thread.currentThread().getName()); }        
});    
}
}
複製程式碼

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

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

執行緒池提供了三個擴充套件點,分別是提交任務的run方法或是call方法被呼叫前與被調後,即beforeExecutor與afaterExecutor方法;另外一個擴充套件點是執行緒池的狀態從TIDYING狀態流轉為TERMINATED狀態時terminated方法會被呼叫。

總結

本來只是想寫一點點,寫著寫著就發現又有點長。這篇主要是介紹了ThreadPoolExecutor中個人認為比較重要點,同時也是把ThreadPoolExecutor再梳理一下發現自己之前理解有偏差的地方。最後歡迎拍磚指正,寫文章不易歡迎轉發。
歡迎關注公眾號洞悉原始碼,會為大家不斷提供優質文章


怎麼才算掌握了JDK中的執行緒池


相關文章