掃二維碼與項(xiàng)目經(jīng)理溝通
我們?cè)谖⑿派?4小時(shí)期待你的聲音
解答本文疑問/技術(shù)咨詢/運(yùn)營(yíng)咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流
本文轉(zhuǎn)載自微信公眾號(hào)「安琪拉的博客」,作者安琪拉的博客 。轉(zhuǎn)載本文請(qǐng)聯(lián)系安琪拉的博客公眾號(hào)。

我們提供的服務(wù)有:做網(wǎng)站、成都做網(wǎng)站、微信公眾號(hào)開發(fā)、網(wǎng)站優(yōu)化、網(wǎng)站認(rèn)證、荔波ssl等。為1000+企事業(yè)單位解決了網(wǎng)站和推廣的問題。提供周到的售前咨詢和貼心的售后服務(wù),是有科學(xué)管理、有技術(shù)的荔波網(wǎng)站制作公司
這篇是并發(fā)編程系列文章第五篇了,說到并發(fā)編程,怎么少的了線程池,在阿里線程池使用場(chǎng)景非常多,用好線程池這個(gè)利器也算是日常開發(fā)必須掌握的了,下面講講2019年的那一夜,就線程池和某位面試官鏖戰(zhàn)了半個(gè)小時(shí)。
面試官 : 看你簡(jiǎn)歷上寫了對(duì)系統(tǒng)性能做了優(yōu)化,能簡(jiǎn)單給我介紹一下嗎?都有哪些優(yōu)化,你是怎么衡量?jī)?yōu)化效果的?
我 : 巴拉巴拉。例如我們系統(tǒng)之前要查詢用戶的個(gè)人身份信息、聯(lián)系人信息、訂單狀態(tài)信息、積分信息,之前系統(tǒng)是單線程串行處理的,我用線程池對(duì)四個(gè)任務(wù)并行處理,然后對(duì)處理結(jié)果合并。
面試官 : 你剛才說用到線程池,能跟我講講為什么用線程池嗎?我創(chuàng)建四個(gè)線程處理可不可以?
我 : 可以,當(dāng)然可以。
我 : 但是用線程池更合適。阿里巴巴開發(fā)規(guī)約中有一條:
3.【強(qiáng)制】線程資源必須通過線程池提供,不允許在應(yīng)用中自行顯式創(chuàng)建線程。
說明:使用線程池的好處是減少在創(chuàng)建和銷毀線程上所消耗的時(shí)間以及系統(tǒng)資源的開銷,解決資源不足的問題。如果不使用線程池,有可能造成系統(tǒng)創(chuàng)建大量同類線程而導(dǎo)致消耗完內(nèi)存或者“過度切換”的問題。
《阿里巴巴研發(fā)手冊(cè)》
我 : 就像你去餐廳吃飯,服務(wù)員總是提前洗好盤子,不會(huì)等你來打飯的時(shí)候才洗盤子,盤子就像是線程池里的線程,你打飯就是要處理的任務(wù)。
面試官 : 那你知道線程池的相關(guān)類關(guān)系嗎?
我: 這算什么問題?不應(yīng)該是問我核心線程數(shù)怎么設(shè)置嗎?好吧。請(qǐng)看下圖:
- public interface Executor {
- void execute(Runnable command);
- }
面試官 : 那你日常開發(fā)中是怎么創(chuàng)建線程池的?
我: 我用ThreadPoolExecutor 自定義創(chuàng)建線程池。
面試官 : 那你知道線程池都有哪些核心參數(shù)嗎?
我: 線程池主要的核心參數(shù)有7個(gè),我們看 ThreadPoolExecutor 構(gòu)造函數(shù)就知道了
面試官 : 老實(shí)說,你是不是來之前背過了,不然怎么可能都記住了。
我: [掀桌子],不面了,還找什么工作,要什么自行車。
我不過是來之前把“安琪拉的博客”公眾號(hào)上的文章都看了個(gè)遍。
面試官 : 其實(shí)剛才那也是問題,考察面試者是否皮實(shí),我們繼續(xù)。
面試官 : 剛才說了這些核心參數(shù),你能不能跟我講講線程池的基本工作原理。
我: 可以的,這里我給你畫個(gè)流程,如下所示:
面試官 : 那按照上面的流程寫段偽代碼。
我: 還能不能好好面了,讓手撕線程池。
那好吧,你對(duì)著的流程圖看,代碼如下:
面試官 : 不錯(cuò),那你平常怎么管理線程池的呢?
我: 我會(huì)搞了個(gè)線程池管理器,比如 ThreadPoolManager,有個(gè)私有變量的Map,按照線程池的作用給他取個(gè)名字,比如起名為: preparePlateThreadPool (準(zhǔn)備餐盤線程池),把線程池名稱定義成常量,和創(chuàng)建好的線程池放到管理器的Map里。
面試官 : 除了你自己用 ThreadPoolExecutor 創(chuàng)建線程池,還有別的方式嗎?
我: java.util.concurrent 包里提供的 Executors 也可以用來創(chuàng)建線程池。
面試官 : Executors 定義了哪幾種 ?
我:
面試官 : 你上面講日常開發(fā)自己 用 ThreadPoolExecutor 創(chuàng)建線程池,為什么不用Executors 提供的。
我: 第一是 Executors 提供的線程池使用場(chǎng)景很有限,一般場(chǎng)景很難用到,第二他們也都是通過 ThreadPoolExecutor 創(chuàng)建的線程池,我直接用 ThreadPoolExecutor 創(chuàng)建線程池,可以理解原理,靈活度更高。
參考阿里開發(fā)手冊(cè)規(guī)約:
4.【強(qiáng)制】線程池不允許使用Executors去創(chuàng)建,而是通過ThreadPoolExecutor的方式,這樣的處理方式讓寫的同學(xué)更加明確線程池的運(yùn)行規(guī)則,規(guī)避資源耗盡的風(fēng)險(xiǎn)。
說明:Executors返回的線程池對(duì)象的弊端如下:
1)FixedThreadPool和SingleThreadPool:
2)CachedThreadPool:
《阿里巴巴研發(fā)手冊(cè)》
面試官 : 前面你代碼里有任務(wù)入隊(duì)的操作,你一般自定義線程池,用的什么隊(duì)列?
我: 這個(gè)要看實(shí)際應(yīng)用的。
面試官 : 那你怎么保證任務(wù)隊(duì)列的可用性呢?
我: 分幾個(gè)方面:
面試官 : 那你怎么合理拆分線程池,核心任務(wù)數(shù)和任務(wù)隊(duì)列大小的呢?
我: 這個(gè)是個(gè)老生常談的問題。
【推薦】 了解每個(gè)服務(wù)大致的平均耗時(shí),可以通過獨(dú)立線程池配置,將較慢的服務(wù)與主線程池隔離開,不致于各服務(wù)線程同歸于盡。
《阿里巴巴研發(fā)手冊(cè)》
這里還有個(gè)公式借鑒:最佳線程數(shù)目 = ((線程等待時(shí)間+線程CPU時(shí)間)/線程CPU時(shí)間 )* CPU數(shù)目
也有開源的輔助測(cè)算線程池的合理線程數(shù)。
面試官 : 那拒絕策略呢?了解嗎
我: 拒絕策略就是當(dāng)任務(wù)太多,超過maximumPoolSize了,只能拒絕。
面試官 : 詳細(xì)講講
我: 拒絕的時(shí)候可以指定拒絕策略,也可以自己實(shí)現(xiàn),JDK默認(rèn)提供了四種拒絕策略.
默認(rèn)拒絕策略, 直接拋RejectedExecutionException
任務(wù)直接丟棄,不拋出異常
由調(diào)用者來執(zhí)行被拒絕的任務(wù),比如主線程調(diào)用線程池的submit提交任務(wù),但是任務(wù)被拒絕,則主線程直接執(zhí)行。
但是線程池如果已經(jīng)被關(guān)閉了,任務(wù)就被丟棄了。
- public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
- //線程池沒關(guān)閉
- if (!e.isShutdown()) {
- //直接run,沒有讓線程池來執(zhí)行
- r.run();
- }
- }
丟棄隊(duì)列里等的最久的任務(wù),然后嘗試執(zhí)行被拒絕的任務(wù)。
但是線程池如果已經(jīng)被關(guān)閉了,任務(wù)就被丟棄了
- public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
- if (!e.isShutdown()) {
- //丟棄隊(duì)列頭部任務(wù)
- e.getQueue().poll();
- //線程池嘗試執(zhí)行任務(wù)
- e.execute(r);
- }
- }
- 面試官 : 那這幾種拒
面試官 : 那這幾種拒絕策略,你選哪一種?
我: 我選拒絕回答
面試官 : 我選你回去等通知。
到年底了,螞蟻現(xiàn)在有些崗位放出來,有挑戰(zhàn)的業(yè)務(wù)場(chǎng)景,6位數(shù)QPS,流程加快,拿完年終,過完年直接來上班,這個(gè)點(diǎn)競(jìng)爭(zhēng)壓力小。
不管想不想來都可以來找我聊聊,我的微信: guofu-angela。

我們?cè)谖⑿派?4小時(shí)期待你的聲音
解答本文疑問/技術(shù)咨詢/運(yùn)營(yíng)咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流