JDK併發包下面的線程池是面試中常常被考查的點,以前我寫過一篇ThreadPoolExecutor源碼分析的文章。由於篇幅有限當時沒說面試中常見的考查點和哪些點是應該掌握。那篇文章着實有點長,更合適用電腦看,結合源碼看。今天,我來談談本身以爲ThreadPoolExecutor哪些點是應該掌握的,這些點應該掌握的點正是面試中常常被問的東西。如今拋出幾個問題,若是你都能答上來,能夠不用往下面看啦。java
若是回答的上就pass吧,哈哈面試
corePoolSize
線程池中的核心線程數mmaximumPoolSize
線程池中的最大線程數keepAliveTime
當線程池中線程數量超過corePoolSize時,容許等待多長時間從workQueue中拿任務unit
keepAliveTime 對應的時間單位,爲TimeUnit類。workQueue
阻塞隊列,當線程池中線程數超過corePoolSize時,用於存儲提交的任務。threadFactory
線程池採用,該線程工廠建立線程池中的線程。handler
爲RejectedExecutionHandler,當線程線中線程超過maximumPoolSize時採用的,拒絕執行處理器。
簡單介紹一下,一個任務提交給線程池後,線程池建立線程來執行提交任務的流程。
一、當提交任務時線程池中的來用執行任務的線程數小於corePoolSize(核心線程數),則線程池利用ThreadFacory(線程工廠)建立線程用於執行提交的任務。不然執行第二2步。
二、當提交任務時線程池中的來用執行任務的線程數大於corePoolSize(核心線程數),但workQueue沒有滿,則線程池會將提交的任務先保存在workQueue(工做隊列),等待線程池中的線程執行完其它已提交任務後會循環從workQueue中取出任務執行。不然執行第3步。
三、當提交任務時線程池中的來用執行任務大於corePoolSize(核心線程數),且workQueu已滿,但沒有超過maximunPoolSize(最大線程數),則線程池利用ThreadFacory(線程工廠)建立線程用於執行提交的任務。不然執行4。
四、當提交任務時線程池中的來用執行任務大於maximunPoolSize,執行線程池中配置的拒絕策略(RejectedExecutionHanlder)。
因此在設置ThreadPoolExecutor的參數時必定要特別當心,不建議採用很大的ArrayBlockQueue或不限大小的LinkedBlockQueue,同時corePoolSize也不該該設置過大。CUP密集的任務的話能夠設置小一點(CUP數據+1這種)避免沒必要要的上下文切換;而對於IO密集的任務則corePoolSize則能夠設置的大一點,能夠避免長時間IO等待而CUP卻空閒。threadFactory建議採用本身定義的,讓其建立的線程容易區分,方便問題定位。併發
ThreadPoolExecutor中的線程哪一個時間點被建立?是任務提交後嗎?能夠在任務提交前建立嗎?
通常在任務被提交後,線程池會利用線程工廠去建立線程,但當線程池中線程數已爲corePoolSize時或maxmumPoolSize時不會。能夠在任務提交前經過prestartCoreThread方法或prestartAllCoreThreads方法預先建立核心線程。具體能夠參考這下這個圖:ide
線程池中線程實現是在addWorker方法中被建立的,詳見以前文章中addWorker方法分析。建立後完,該線程就被啓動。線程池中被建立的線程被封裝到了Worker對象中,而Worker類又實現了Runnable接口,線程池中的線程又引用了worker。當線程被start後實際就有機會等待操做系統調度執行Worker類的run方法。源碼分析
Worker(Runnable firstTask) { setState(-1); this.firstTask = firstTask; //建立的線程引用了worker this.thread = getThreadFactory().newThread(this); }
一旦線程池經過ThreadFactory建立好線程後,就會將建立的線程封裝到了Worker對象中,同時啓動該線程。新建立的線程會執行剛提交的任務,同時會不斷地從workerQueue中取出任務執行。線程池的線程複用正是經過不斷地從workerQueue中取出任務來執行達到的。源碼分析見runWorkers方法分析。this
同時一時刻不能執行多個任務,只有一個任務執行完時才能去執行另外一個任務。上面說到線程池中經過ThreadFacory建立的線程最後會被封裝到Worker中,而該線程又引用了Worker,start線程後,任務實際上是在Worker中的run方法中被執行,最終run又將任務執行代理給ThreadPoolExecutor的runWorker方法。操作系統
private final class Worker extends AbstractQueuedSynchronizer implements Runnable {...}
Worder一方面實現了Runnable,另外一方面又繼承了AQS。經過實現AQS,Worker具備了排它鎖的語義,每次在執行提交任務時都會先lock操做,執行完任務後再作unlock操做。正是這個加鎖與解鎖的操做,保證了同一個線程要執行完當前任務纔有機再去執行另外一個任務。.net
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(); } }
若是沒指核心線程容許超時將會有問題。核心線程容許超時是指在從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方法適用於不關心提交任務是否執行完的場景。
線程池提供了三個擴展點,分別是提交任務的run方法或是call方法被調用前與被調後,即beforeExecutor與afaterExecutor方法;另一個擴展點是線程池的狀態從TIDYING狀態流轉爲TERMINATED狀態時terminated方法會被調用。
原本只是想寫一點點,寫着寫着就發現又有點長。這篇主要是介紹了ThreadPoolExecutor中我的認爲比較重要點,同時也是把ThreadPoolExecutor再梳理一下發現本身以前理解有誤差的地方。
若是你以爲這篇內容對你還蠻有幫助,我想邀請你幫我三個小忙:
點贊,轉發,有大家的 『點贊和評論』,纔是我創造的動力。
關注公衆號 『 java爛豬皮 』,不按期分享原創知識。
同時能夠期待後續文章ing🚀
做者:葉易
出處:club.perfma.com/article/191…