線程池調優(yōu)原來這么簡單!
點擊上方“Java金融”,選擇“設為星標”
后臺回復"888"獲取bat面試題集

原文|https://t.hk.uy/awez
背景:
最近的一個項目需要用到招標,臨時加了給我們的系統(tǒng)增加了一個性能需求,多少呢?一秒鐘300次NTP(不知道ntp的同學可以百度一下),平均3ms一次啊,沒測試過,心里沒有底。(⊙o⊙)…
情境介紹:
系統(tǒng)是一個時間服務器系統(tǒng),客戶端就是window系統(tǒng),或者其他的一些服務器,來向時間服務器同步時間。

默認的window會向這個time.winodows.com進行時間同步,當然你也可以換成其他時間同步服務器。
劃重點了:服務端NTP接口采用的是netty框架寫的一個接口,netty想必大家都了解的吧,nio通信,性能超好的。
測試代碼是使用Executors.newFixedThreadPool寫的客戶端,10個線程數發(fā)送ntp包
第一次測試
數據庫連連接池最大設置為40個,測試結果倆一秒鐘28次,是的你沒有看錯,連十分之一都沒有,怎么這么差勁啊
達不到預期啊,不行啊,這不就達不到要求的了嗎,得改啊,哪里改啊,怎么改啊?回到代碼中去,順藤摸瓜找到具體業(yè)務類,就是繼承SimpleChannelInboundHandler的類,從頭到尾打量了一下業(yè)務代碼,發(fā)現業(yè)務主要是構造返回消息,記錄日志。構造返回就是Java里的構造對象什么的,根本不耗時的。
那就想不是有記錄日志嗎,不往數據庫里面寫東西了,把它注釋掉,跑一遍試試看。
第二遍測試
哎呦媽呀,起飛了老鐵,直接飆到了每秒鐘2萬次。
看到這個令人驚訝的數據,這遠遠超過要求啊,哈哈哈,妥妥的。突然一想,不對啊,這好像和產品設計不符合啊,設計里是要求記錄日志的啊,這個是有點濫竽充數啊,不行不行,這個得改。仔細分析一下,日志得寫到數據庫,讀了《java高并發(fā)程序設計》,心想是不是可以用異步的方式記錄日志呢,弄一個線程池吧。阿里巴巴JAVA開發(fā)手冊里是不推薦使用Executors中的現成的線程池的(具體原因我就不說了,可以看一下),那就自己寫一個吧。
第三次測試
考慮到任務提交速度快的原因,第一次構造線程池采用了直接提交的隊列,這樣任務處理的快一些
private?static?ExecutorService?saveThreadPool?=?new?ThreadPoolExecutor(2,?100,?60L,?TimeUnit.SECONDS,
new?SynchronousQueueExecutors.defaultThreadFactory(),
new?ThreadPoolExecutor.DiscardPolicy());
測試代碼再跑一遍,哎呦媽呀,又起飛了老鐵!
心想我這么牛逼的嗎,這隨便寫個線程池就OK了。
在服務器執(zhí)行了top一看,不得了:
這個cpu直接占滿了,系統(tǒng)里還有其他服務的,不能把資源全都給它啊。這次留了個心眼看了下數據庫日志,不對啊,沒有全部記錄啊,尼瑪發(fā)了一百多萬結果只記錄了幾萬條,這個差太多了啊。為什么漏掉了呢,回過頭來繼續(xù)看線程池的構造。終于發(fā)現了紕漏,拒絕策略是用了new ThreadPoolExecutor.DiscardPolicy(),這個可出事了啊,不行不行,這直接丟棄了,記錄日志的任務不能直接丟棄。
第四次測試
這次又把書拿出來看了看,看了一些大牛寫的線程池構造,最終敲定了這樣的構造方式。
private?static?ExecutorService?saveThreadPool?=?new?ThreadPoolExecutor(2,?40,?60L,?TimeUnit.SECONDS,
????????????new?LinkedBlockingQueue(50000),?Executors.defaultThreadFactory(),
????????????new?ThreadPoolExecutor.CallerRunsPolicy());
采用LinkedBlockingQueue,最多可有50000個任務在阻塞隊列中,線程池最大值設置40個,核心池大小設置2個,多出來的38個線程最多活躍60秒就會被回收。
拒絕采用CallerRunsPolicy,不會丟棄任務,只要線程池未關閉,該策略直接在調用者線程中,運行當前被丟棄的任務。顯然這樣做不會真的丟棄任務,但是,任務提交線程的性能極有可能會急劇下降。在代碼測試中,執(zhí)行了top,發(fā)現cpu的利用率穩(wěn)定在24%左右,這個可以接受
測試結果:
每秒鐘1千2,這個也遠遠超過了性能指標,而且日志也全都記錄到數據中,不過這個不是及時性的,它會在測試程序結束1分鐘后,才會完成數據如插入,這個和隊列任務有關。
總結:
這個線程池我是沒有關閉的,因為每次任務提交后隊列中還有很多任務,如果關閉的話,每次在開啟一個線程池會降低速度,所以這個就不關閉了吧 如果有大神看出什么端倪的話,歡迎批評斧正,繼續(xù)優(yōu)化,個人感覺還有提升空間。
最后
線程池的知識在工作中還是比較重要的,所以還是有必要去掌握的,上面的方案應該還可以采用寫數據的時候可以考慮批量插入數據庫。這樣數據庫的吞吐量會大大提高,比如一次寫入5000條,不過這個可能會存在數據丟失,比如應用重啟的時候。不過對于日志來說的話,應該是允許丟失的,具體操作的話這個還是需要由不同的業(yè)務來決定。最后也推薦下以前寫的兩篇關于線程池的文章《【Java并發(fā)編程】面試必備之線程池》、《【Java并發(fā)編程】從源碼分析幾道必問線程池的面試題?》
往期精選
最近面試BAT,整理一份面試資料《Java面試BATJ通關手冊》,覆蓋了Java核心技術、JVM、Java并發(fā)、SSM、微服務、數據庫、數據結構、等等。獲取方式:點“在看”,關注公眾號并回復 666?領取,更多內容陸續(xù)奉上。
文章有幫助的話,在看,轉發(fā)吧。
謝謝支持喲 (*^__^*)
