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

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":2}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"JDK併發包下面的線程池是面試中經常被考查的點,之前我寫過一篇"},{"type":"link","attrs":{"href":"https://club.perfma.com/article/1719588","title":null},"content":[{"type":"text","text":"ThreadPoolExecutor源碼分析"}]},{"type":"text","text":"的文章。因爲篇幅有限當時沒說面試中常見的考查點和哪些點是應該掌握。那篇文章着實有點長,更合適用電腦看,結合源碼看。今天,我來談談自己覺得ThreadPoolExecutor哪些點是應該掌握的,這些點應該掌握的點正是面試中經常被問的東西。現在拋出幾個問題,如果你都能答上來,可以不用往下面看啦。"}]},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"ThreadPoolExecutor中常用參數有哪些,作用是什麼?任務提交後,ThreadPoolExecutor會按照什麼策略去創建線程用於執行提交任務?"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"ThreadPoolExecutor有哪些狀態,狀態之間流轉是什麼樣子的?"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"ThreadPoolExecutor中的線程哪個時間點被創建?是任務提交後嗎?可以在任務提交前創建嗎?"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"ThreadPoolExecutor中創建的線程哪個時間被啓動?"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":5,"align":null,"origin":null},"content":[{"type":"text","text":"ThreadPoolExecutor竟然是線程池那麼他是如何做到重複利用線程的?"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":6,"align":null,"origin":null},"content":[{"type":"text","text":"ThreadPoolExecutor中創建的同一個線程同一時刻能執行多個任務嗎?如果不能是通過什麼機制保證ThreadPoolExecutor中的同一個線程只能執行完一個任務,纔會機會去執行另一個任務?"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":7,"align":null,"origin":null},"content":[{"type":"text","text":"ThreadPoolExecutor中關閒線程池的方法shutdown與shutdownNow的區別是什麼?"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":8,"align":null,"origin":null},"content":[{"type":"text","text":"通過submit方法向ThreadPoolExecutor提交任務後,當所有的任務都執行完後不調用shutdown或shutdownNow方法會有問題嗎?"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":9,"align":null,"origin":null},"content":[{"type":"text","text":"ThreadPoolExecutor有沒有提供擴展點,方便在任務執行前或執行後做一些事情?"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果回答的上就pass吧,哈哈"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"ThreadPoolExecutor參數有哪些與創建線程策略?"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"ThreadPoolExecutor參數"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"corePoolSize"}]},{"type":"text","text":" 線程池中的核心線程數"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"mmaximumPoolSize"}]},{"type":"text","text":" 線程池中的最大線程數"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"keepAliveTime"}]},{"type":"text","text":" 當線程池中線程數量超過corePoolSize時,允許等待多長時間從workQueue中拿任務"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"unit"}]},{"type":"text","text":" keepAliveTime 對應的時間單位,爲TimeUnit類。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"workQueue"}]},{"type":"text","text":" 阻塞隊列,當線程池中線程數超過corePoolSize時,用於存儲提交的任務。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"threadFactory"}]},{"type":"text","text":" 線程池採用,該線程工廠創建線程池中的線程。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"handler"}]},{"type":"text","text":" 爲RejectedExecutionHandler,當線程線中線程超過maximumPoolSize時採用的,拒絕執行處理器。"}]}]}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"創建線程策略"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/07/07fb9ec57f20d5d505261bc6af88236b.png","alt":"image.png","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"簡單介紹一下,一個任務提交給線程池後,線程池創建線程來執行提交任務的流程。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1、當提交任務時線程池中的來用執行任務的線程數小於corePoolSize(核心線程數),則線程池利用ThreadFacory(線程工廠)創建線程用於執行提交的任務。否則執行第二2步。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2、當提交任務時線程池中的來用執行任務的線程數大於corePoolSize(核心線程數),但workQueue沒有滿,則線程池會將提交的任務先保存在workQueue(工作隊列),等待線程池中的線程執行完其它已提交任務後會循環從workQueue中取出任務執行。否則執行第3步。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3、當提交任務時線程池中的來用執行任務大於corePoolSize(核心線程數),且workQueu已滿,但沒有超過maximunPoolSize(最大線程數),則線程池利用ThreadFacory(線程工廠)創建線程用於執行提交的任務。否則執行4。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"4、當提交任務時線程池中的來用執行任務大於maximunPoolSize,執行線程池中配置的拒絕策略(RejectedExecutionHanlder)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以在設置ThreadPoolExecutor的參數時一定要特別小心,不建議採用很大的ArrayBlockQueue或不限大小的LinkedBlockQueue,同時corePoolSize也不應該設置過大。CUP密集的任務的話可以設置小一點(CUP數據+1這種)避免不必要的上下文切換;而對於IO密集的任務則corePoolSize則可以設置的大一點,可以避免長時間IO等待而CUP卻空閒。threadFactory建議採用自己定義的,讓其創建的線程容易區分,方便問題定位。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"線程池有哪些狀態,狀態之間流轉是什麼樣子的?"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"RUNNING:運行中,接收新的任務或處理隊列中的任務。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"SHUTDOWN:關閉,不再接收新的任務,但會處理隊列中的任務值爲0。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"STOP:停止,不再接收新的任務,也不處理隊列中的任務,並中斷正在處理的任務。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"TIDYING:所有任務已結束隊列大小爲0,轉變TIDYING狀態的線程將會執行terminated()方法。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"TERMINATED:結束terminated()已被執行完。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"狀態流程如下圖:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/1a/1a2185e358c91e9a33250727fed341c9.png","alt":"image.png","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"池程池中的線程哪個時間點被創建?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ThreadPoolExecutor中的線程哪個時間點被創建?是任務提交後嗎?可以在任務提交前創建嗎?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一般在任務被提交後,線程池會利用線程工廠去創建線程,但當線程池中線程數已爲corePoolSize時或maxmumPoolSize時不會。可以在任務提交前通過prestartCoreThread方法或prestartAllCoreThreads方法預先創建核心線程。具體可以參考這下這個圖:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/bc/bcd257a31da9b57e1656e95392d5e53a.png","alt":"image.png","title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"ThreadPoolExecutor中創建的線程哪個時間被啓動?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"線程池中線程實現是在addWorker方法中被創建的,詳見之前文章中addWorker方法分析。創建後完,該線程就被啓動。線程池中被創建的線程被封裝到了Worker對象中,而Worker類又實現了Runnable接口,線程池中的線程又引用了worker。當線程被start後實際就有機會等待操作系統調度執行Worker類的run方法。"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"Worker(Runnable firstTask) {\n setState(-1); \n this.firstTask = firstTask;\n //創建的線程引用了worker\n this.thread = getThreadFactory().newThread(this);\n}\n複製代碼"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"ThreadPoolExecutor竟然是線程池那麼他是如何做到重複利用線程的?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一旦線程池通過ThreadFactory創建好線程後,就會將創建的線程封裝到了Worker對象中,同時啓動該線程。新創建的線程會執行剛提交的任務,同時會不斷地從workerQueue中取出任務執行。線程池的線程複用正是通過不斷地從workerQueue中取出任務來執行達到的。源碼分析見runWorkers方法分析。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"ThreadPoolExecutor中創建的同一個線程同一時刻能執行多個任務嗎?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"同時一時刻不能執行多個任務,只有一個任務執行完時才能去執行另一個任務。上面說到線程池中通過ThreadFacory創建的線程最後會被封裝到Worker中,而該線程又引用了Worker,start線程後,任務其實是在Worker中的run方法中被執行,最終run又將任務執行代理給ThreadPoolExecutor的runWorker方法。"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"private final class Worker\n extends AbstractQueuedSynchronizer\n implements Runnable\n {...}\n複製代碼"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Worder一方面實現了Runnable,另一方面又繼承了AQS。通過實現AQS,Worker具有了排它鎖的語義,每次在執行提交任務時都會先lock操作,執行完任務後再做unlock操作。正是這個加鎖與解鎖的操作,保證了同一個線程要執行完當前任務纔有機再去執行另一個任務。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"ThreadPoolExecutor中關閒線程池的方法shutdown與shutdownNow的區別是什麼?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"shutdown方法是將線程池的狀態設置爲SHUTDOWN,此時新任務不能被提交(提交會拋出異常),workerQueue的任務會被繼續執行,同時線程池會向那些空閒的線程發出中斷信號。空閒的線程實際就不沒在執行任務的線程。如何被封裝在worker裏的線程能加鎖,這裏這個線程實現會就空閒的。下面是向空閒的線程發出中斷信號源碼。"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":" private void interruptIdleWorkers(boolean onlyOne) {\n final ReentrantLock mainLock = this.mainLock;\n mainLock.lock();\n try {\n for (Worker w : workers) {\n Thread t = w.thread;\n //w.tryLock()用於加鎖,看線程是否在執行任務\n if (!t.isInterrupted() && w.tryLock()) {\n try {\n t.interrupt();\n } catch (SecurityException ignore) {\n } finally {\n w.unlock();\n }\n }\n if (onlyOne)\n break;\n }\n } finally {\n mainLock.unlock();\n }\n }\n複製代碼"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"shutdownNow方法是將線程池的狀態設置爲STOP,此時新任務不能被提交(提交會拋出異常),線程池中所有線程都會收到中斷的信號。具體線程會作出什麼響應,要看情況,如果線程因爲調用了Object的wait、join方法或是自身的sleep方法而阻塞,那麼中斷狀態會被清除,同時拋出InterruptedException。其它情況可以參考Thread.interrupt方法的說明。shutdownNow方法向所有線程發出中斷信息源碼如下:"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"private void interruptWorkers() {\n final ReentrantLock mainLock = this.mainLock;\n //加鎖操作保證中斷過程中不會新woker被創建\n mainLock.lock();\n try {\n for (Worker w : workers)\n w.interruptIfStarted();\n } finally {\n mainLock.unlock();\n }\n}\n複製代碼"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"通過submit方法向ThreadPoolExecutor提交任務後,當所有的任務都執行完後不調用shutdown或shutdownNow方法會有問題嗎?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果沒指核心線程允許超時將會有問題。核心線程允許超時是指在從wokerQueue中獲取任務時,採用的阻塞的獲取方式等待任務到來,還是通過設置超時的方式從同步阻塞隊列中獲取任務。即是通通過BlockingQueue的poll方法獲取任務還是take方法獲取任務。可參考之前的源碼分析中的getTask方法分析。如果不調用shutdown或shutdownNow方法,核心線程由於在getTask方法調用BlockingQueue.take方法獲取任務而處於一直被阻塞掛起狀態。核心線程將永遠處於Blocking的狀態,導致內存泄漏,主線程也無法退出,除非強制kill。試着運行如下程序會發現,程序無法退出。"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"public class Test {\n public static void main(String args[]) {\n ExecutorService executorService = new ThreadPoolExecutor(3, 3,10L, TimeUnit.SECONDS, new ArrayBlockingQueue(10));\n executorService.submit(new Runnable() {\n @Override\n public void run() {\n System.out.println(\"thread name \" + Thread.currentThread().getName());\n }\n });\n }\n}\n複製代碼"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所在使用線程池時一定要記得根本具體場景調用shutdown或shutdownNow方法關閉線程池。shutdown方法適用於提交任務都要被執行完的場景,shutdownNow方法適用於不關心提交任務是否執行完的場景。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"ThreadPoolExecutor有沒有提供擴展點,方便在任務執行前或執行後做一些事情?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"線程池提供了三個擴展點,分別是提交任務的run方法或是call方法被調用前與被調後,即beforeExecutor與afaterExecutor方法;另外一個擴展點是線程池的狀態從TIDYING狀態流轉爲TERMINATED狀態時terminated方法會被調用。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"總結"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本來只是想寫一點點,寫着寫着就發現又有點長。這篇主要是介紹了ThreadPoolExecutor中個人認爲比較重要點,同時也是把ThreadPoolExecutor再梳理一下發現自己之前理解有偏差的地方。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"看完三件事❤️"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果你覺得這篇內容對你還蠻有幫助,我想邀請你幫我三個小忙:"}]},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"點贊,轉發,有你們的 『點贊和評論』,纔是我創造的動力。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"關注公衆號 『 "},{"type":"text","marks":[{"type":"strong"}],"text":"java爛豬皮"},{"type":"text","text":" 』,不定期分享原創知識。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"同時可以期待後續文章ing🚀"}]}]}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/34/34172ad7f3cc8e0f28bd1fc6ca2d2b68.png","alt":null,"title":"","style":[{"key":"width","value":"50%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"作者:葉易"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"出處:"},{"type":"link","attrs":{"href":"https://club.perfma.com/article/1917631","title":null},"content":[{"type":"text","text":"club.perfma.com/article/191…"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章