5000字、12 連環(huán)炮、一張圖快速搞定線程池
寫在前面
前面文章中,我們總結(jié)了JVM18連環(huán)炮、并發(fā)并最基礎(chǔ)的12連環(huán)炮,建議先閱讀:
連環(huán)炮繼續(xù)走起,今天我給大家總結(jié)了線程池的12連環(huán)炮。
1、為什么要?jiǎng)?chuàng)建線程池?
2、創(chuàng)建線程池有哪些方式?
3、Executors能創(chuàng)建幾種常用線程池?
4、線程池有哪些參數(shù)?
5,能說說線程池原理嗎?
6、線程池有哪些拒絕策略?
7、線程池中使用到了阻塞隊(duì)列,那你知道有哪些阻塞隊(duì)列?
8、線程池中的核心線程如何設(shè)置呢?
9、知道線程池有哪些狀態(tài)嗎?
10、線程池中的線程是如何復(fù)用的?
11、Java線程池中submit() 和 execute()方法有什么區(qū)別?
12、在工作中,有使用過線程池嗎?
1、為什么要?jiǎng)?chuàng)建線程池?
學(xué)了線程不懂線程池,那是遠(yuǎn)遠(yuǎn)不夠的,因?yàn)槲覀兺ǔJ褂镁€程的時(shí)候,都會(huì)涉及到線程池。
數(shù)據(jù)庫也有連接池,也就是大家所講的池化技術(shù)。
線程也有線程池,對于池的概念大家一定把握好兩個(gè)關(guān)鍵:
- 控制資源的數(shù)量
- 重復(fù)利用資源
為什么要?jiǎng)?chuàng)建線程池,回答這個(gè)問題,我們可以說一下它的三個(gè)優(yōu)點(diǎn)。
第一個(gè),降低資源消耗。通過重復(fù)利用線程池中的線程,能規(guī)避我們不斷創(chuàng)建線程和不斷銷毀線程造成的資源消耗。
第二個(gè),提高響應(yīng)速度。當(dāng)任務(wù)到達(dá)線程池中,任務(wù)可以不需要等待線程創(chuàng)建就能立即執(zhí)行。
第三個(gè),提高線程的可管理性。大家都知道,線程是稀缺資源,如果無限制的創(chuàng)建,不僅會(huì)消耗系統(tǒng)資源,還會(huì)降低系統(tǒng)的穩(wěn)定性。使用線程池,可以進(jìn)行統(tǒng)一的分配、調(diào)優(yōu)和監(jiān)控。
回答完上面三個(gè)線程池的好處以后,面試官肯定會(huì)繼續(xù)問:創(chuàng)建線程池的方式有哪些?
2、創(chuàng)建線程池有哪些方式?
在java中創(chuàng)建線程有兩種方式:
- Executors
- ThreadPoolExecutor
這兩個(gè)都在juc目錄下面。其中,Executors可以理解為一個(gè)工具類,是用來生成線程池的。另外,Executors 的大部分創(chuàng)建線程方式最終使用的都是ThreadPoolExecutor。
并且在阿里規(guī)開發(fā)規(guī)范中,建議不要使用Executors 來創(chuàng)建線程池。因?yàn)榫€程池的類型以及線程相關(guān)參數(shù)都是在Executors 中就已經(jīng)封裝好了,如果我們在代碼中使用不當(dāng),可能會(huì)造成系統(tǒng)出問題。建議使用ThreadPoolExecutor創(chuàng)建線程,然后,根據(jù)自己的業(yè)務(wù)實(shí)際情況進(jìn)行創(chuàng)建線程池。
3、Executors能創(chuàng)建幾種常用線程池?
上面我們說到,創(chuàng)建線程池的兩種方式。
其實(shí),大部分面試官想要我們回答的是Executors 創(chuàng)建線程池的方式有哪幾種?所以肯定會(huì)繼續(xù)追問,Executors 創(chuàng)建線程池的幾種方式。今天就來看看,如何回答這個(gè)問題。
我們可以把Executors 類理解成為一個(gè)工廠類,該類可以為我們提供六種創(chuàng)建線程池的方法:
第1種,newSingleThreadExecutor,創(chuàng)建了一個(gè)單線程的線程池,此線程池保證了所有任務(wù)的執(zhí)行順序都是按照任務(wù)的提交順序執(zhí)行。也就是說,這個(gè)池子里只有一個(gè)線程。
第2種,newFixedThreadPool,創(chuàng)建指定數(shù)量線程的線程池;
第3種,newCachedThreadPool,創(chuàng)建一個(gè)可緩存的線程池,伸縮性、動(dòng)態(tài)調(diào)整,60s回收一次;
第4種,newScheduledThreadPool,創(chuàng)建一個(gè)大小無限的線程池;
第5種,newSingleThreadScheduledExecutor(),創(chuàng)建一個(gè)單例線程池,定期或延時(shí)執(zhí)行任務(wù),很多框架中使用此實(shí)現(xiàn)定時(shí)心跳檢測。
第6種,newWorkStealingPool,這是Java 8 新增創(chuàng)建線程池的方法。
回答完上面內(nèi)容后,面試就會(huì)問:線程池有哪些參數(shù)?
4、線程池有哪些參數(shù)?
這個(gè)問題也算是老掉牙的面試題,但是面試中頻率是相當(dāng)高,經(jīng)久不衰。
線程池一共有7個(gè)參數(shù):
第1個(gè),maximumPoolSize,指的是最大線程數(shù)
第2個(gè),corePoolSize,指的是核心線程數(shù)
第3個(gè),keepAliveTime ,指的是最大線程活躍時(shí)間
第4個(gè),unit: 指定了keepAliveTime的單位,可以為毫秒,秒,分,小時(shí)等;
第5個(gè),workQueue: 存儲未執(zhí)行的任務(wù)的隊(duì)列;
第6個(gè),threadFactory: 創(chuàng)建線程的工廠,如果未指定則使用默認(rèn)的線程工廠;
第7個(gè),handler: 指定了當(dāng)任務(wù)隊(duì)列已滿,并且沒有可用線程執(zhí)行任務(wù)時(shí)對新添加的任務(wù)的處理策略;
面試中,如果你能說出maximumPoolSize、corePoolSize、keepAliveTime 、workQueue、handler五個(gè)核心參數(shù)也算可以了,但還是建議大家把七個(gè)參數(shù)都回答出來。
5,能說說線程池原理嗎?
這道題,我本人在面試中最喜歡用生活中的例子來回答,請看我是怎么回答的:
一家工廠,訂單來了,正式員工們就開始生產(chǎn)零件。但是訂單越來越多,正式員工處理不來了,就先把任務(wù)放到倉庫里,但是遇到訂單爆棚的時(shí)候,倉庫也都放不下了,這時(shí)候,工廠就會(huì)想到找點(diǎn)臨時(shí)工來幫忙生產(chǎn)零件,如果訂單實(shí)在是太多了,這時(shí)候工廠可能就會(huì)想辦法拒絕那些不是很有利潤的訂單。同時(shí),生意也有慘淡的時(shí)候,這時(shí)候工廠也許就會(huì)清理臨時(shí)工,讓他們等以后忙的時(shí)候再來幫忙。
在這里,訂單就是我們創(chuàng)建的線程,工廠就就是線程池,正式員工就是核心線程,臨時(shí)員工就是最大線程,倉庫就是阻塞隊(duì)列,訂單實(shí)在太多了,就會(huì)考慮到訂單的利潤,也就是說,利潤太少了那就不干了,這就是拒絕策略。
建議結(jié)合前面整體圖進(jìn)行理解。
6、線程池有哪些拒絕策略?
線程池核心參數(shù)中,有個(gè)很重要的參數(shù)就是拒絕策略,面試官也是非常喜歡問的,大家可以這么回答。
線程池中主要有4種拒絕策略:
第1種,AbortPolicy,這是默認(rèn)策略,指的是丟棄任務(wù),拋出異常;
第2種,CallerRunsPolicy,簡單的說,就是后面排隊(duì)的線程就在那等著,被拒絕的任務(wù)在主線程中運(yùn)行,所以主線程就被阻塞了,別的任務(wù)只能在被拒絕的任務(wù)執(zhí)行完后,才會(huì)繼續(xù)被提交到線程池中。
第3種,DiscardOldestPolicy,指的是丟棄等待隊(duì)列中最久的任務(wù),并且執(zhí)行當(dāng)前任務(wù);
第4種,DiscardPolicy,直接丟棄任務(wù),也不拋異常。
我們說完了線程池核心參數(shù)中一個(gè)拒絕策略,也有的面試官喜歡問另外一個(gè)參數(shù),那就是阻塞隊(duì)列。
7、線程池中使用到了阻塞隊(duì)列,那你知道有哪些阻塞隊(duì)列?
這個(gè)問題,也屬于java集合框架的內(nèi)容,所以,還是很有必要掌握的。
JDK7及以后一共有7種阻塞隊(duì)列:
第1種,ArrayBlockingQueue ,由數(shù)組結(jié)構(gòu)組成的有界阻塞隊(duì)列。
第2種,LinkedBlockingQueue ,由鏈表結(jié)構(gòu)組成的有界阻塞隊(duì)列。
第3種,PriorityBlockingQueue ,支持優(yōu)先級排序的無界阻塞隊(duì)列。
第4種,DelayQueue,使用優(yōu)先級隊(duì)列實(shí)現(xiàn)的無界阻塞隊(duì)列。
第5種,SynchronousQueue,不存儲元素的阻塞隊(duì)列。
第6種,LinkedTransferQueue,由鏈表結(jié)構(gòu)組成的無界阻塞隊(duì)列。
第7種,LinkedBlockingDeque,由鏈表結(jié)構(gòu)組成的雙向阻塞隊(duì)列。
另外,Executors 中使用最多的是LinkedBlockingDeque,還用了SynchronousQueue。
回答完以上七種后,請記住把Executors 中使用的兩種也強(qiáng)調(diào)一下,表示你對此非常熟悉。
還有可能會(huì)繼續(xù)問一個(gè)非常重要的參數(shù),那就是核心線程數(shù)。
8、線程池中的核心線程如何設(shè)置呢?
回答這個(gè)問題,我們首先得知道,線程和CPU有關(guān),所以,如何設(shè)置核心線程數(shù),肯定也是和CPU有關(guān)的,核心線程數(shù)設(shè)置和任務(wù)的類型也有關(guān),任務(wù)類型有兩種:
一種是CPU密集型, 比如像加解密、壓縮、計(jì)算等一系列需要大量耗費(fèi) CPU 資源的任務(wù),大部分場景下都是純 CPU 計(jì)算。所以,核心線程數(shù)可以設(shè)置為:核心線程數(shù)=CPU個(gè)數(shù)+1。
另外一種,就是IO密集型, 比如像 MySQL 數(shù)據(jù)庫、文件的讀寫、網(wǎng)絡(luò)通信等任務(wù),這類任務(wù)不會(huì)特別消耗 CPU 資源,但是 IO 操作比較耗時(shí),會(huì)占用比較多的時(shí)間。所以,核心線程數(shù)可以設(shè)置為:核心線程數(shù) = CPU個(gè)數(shù)*2。
這個(gè)只是個(gè)理論值,具體設(shè)置大小,建議在本地、測試、準(zhǔn)生產(chǎn)環(huán)境下調(diào)試出相對最優(yōu)參數(shù)大小。
回答完這些后,有的面試官可能會(huì)問:你知道線程池的狀態(tài)嗎?
9、知道線程池有哪些狀態(tài)嗎?
很多人,平時(shí)估計(jì)都不知道線程池居然也有狀態(tài),都只知道線程狀態(tài)。所以這種問題如果不準(zhǔn)備,只要被問到必掛。
線程池有5種狀態(tài),
第1種,RUNNING,指的是線程池的初始化狀態(tài),可添加待執(zhí)行的任務(wù)。
第2種,SHUTDOWN,指的是線程池處于待關(guān)閉狀態(tài),不接收新任務(wù),僅處理已接收的任務(wù)。
第3種,STOP,指的是線程池立即關(guān)閉,不接收新的任務(wù),放棄緩存隊(duì)列中的任務(wù)并且中斷正在處理的任務(wù)。
第4種,TIDYING,指的是線程池自主整理狀態(tài),我們可以調(diào)用 terminated() 方法進(jìn)行線程池整理。
第5種,TERMINATED,指的是線程池終止?fàn)顟B(tài)。
回答完線程池狀態(tài)后,下面這個(gè)問題是非常經(jīng)典的,也基本上是問中高級的。
10、線程池中的線程是如何復(fù)用的?
這里的線程復(fù)用指的是線程池的中線程被重復(fù)利用,這里注意了,很多人也許都還沒明白這點(diǎn)!再強(qiáng)調(diào)一次,
我們手動(dòng)創(chuàng)建的線程放到線程池中,其實(shí),這時(shí)候它變成了一個(gè)任務(wù)。
線程池執(zhí)行這些任務(wù)的時(shí)候,不再是調(diào)用線程(任務(wù))的start()方法,而是由線程池中的線程去執(zhí)行這些任務(wù)。
然后線程池中的這些線程就不斷地去循環(huán),執(zhí)行我們丟進(jìn)去的任務(wù),并調(diào)用這些任務(wù)的run()方法。此時(shí)調(diào)用的run()方法就相當(dāng)于調(diào)用一個(gè)普通對象的put方法,通過重復(fù)使用這些固定線程來執(zhí)行所有任務(wù)。
11、Java線程池中submit() 和 execute()方法有什么區(qū)別?
不知道你是否還記得,我們創(chuàng)建線程,然后把線程提交到線程池中時(shí)候,通常有兩種方法:submit和execute。所以面試官很有可能會(huì)問這個(gè)問題,這個(gè)問題也很有可能出現(xiàn)在筆試中。
回答這個(gè)問題,我們得先說說兩者的相同點(diǎn),
submit() 和 execute()方法都是用來提交任務(wù)的,指的是把我們創(chuàng)建的任務(wù)提交到線程池中。
兩者的不同點(diǎn)在于,調(diào)用 execute() 方法提交任務(wù)不能拿到任務(wù)的返回值,而調(diào)用 submit() 可以使用 Future 接收線程池執(zhí)行任務(wù)的返回值。
這里就可以聯(lián)想到,我們創(chuàng)建線程的方式中,有的方式可以拿到線程返回值,有的拿不到。
另外,execute()方法是ThreadPoolExecutor的方法,而submit() 方法是ThreadPoolExecutor的父類AbstractExecutorService中的方法。
12、在工作中,有使用過線程池嗎?
這道問題,對于99%的人都很害怕,因?yàn)楣ぷ髦锌赡苷娴臎]用過,所以,心里很沒底兒。有的人用過了,卻沒注意項(xiàng)目中是怎么樣用,可能是組長或者其他人寫了個(gè)工具類,自己用也是拷貝別人代碼改改,然后也挺好的。
其實(shí)大家可以編造自己的業(yè)務(wù)場景哈,即使沒有,你也可以自己編造點(diǎn)呀,不圖羊嘛。這世道就是套路多多滴。這個(gè)沒有具體答案,答案還得靠你結(jié)合你所做項(xiàng)目來回答哈。
免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺僅提供信息存儲服務(wù)。文章僅代表作者個(gè)人觀點(diǎn),不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!