多線程、線程池、併發包每當談起這些詞彙,可能不是在面試就是在準備面試的路上了。面試
有句話叫「面試造航母,工做擰螺絲「,確實不少狀況下咱們是用不到這些東西的,可是學好這些東西對咱們的平常工做也可能會產生意想不到的好處的。sql
臨近年底,收拾了下手頭工做,趁着最後兩天有些閒暇,準備着手優化下前段時間業務人員反饋的部分報表導出速度過慢的問題。數據庫
報表的優化主要是涉及兩個方面,一個是SQL和數據庫層面的優化,另外一個就是代碼層面的優化了,本文主要講述代碼層面利用多線程處理的一點小總結。api
newSingleThreadExecutor:一個單線程的線程池,能夠用於須要保證順序執行的場景,而且只有一個線程在執行。多線程
newFixedThreadPool:一個固定大小的線程池,能夠用於已知併發壓力的狀況下,對線程數作限制。併發
newCachedThreadPool:一個能夠無限擴大的線程池,比較適合處理執行時間比較小的任務。ide
newScheduledThreadPool:能夠延時啓動,定時啓動的線程池,適用於須要多個後臺線程執行週期任務的場景。學習
newWorkStealingPool:一個擁有多個任務隊列的線程池,能夠減小鏈接數,建立當前可用cpu數量的線程來並行執行。優化
在實際的使用過程當中,通常咱們都是用Executors去建立線程池,若是有一些其餘的需求,好比指定線程池的拒絕策略,阻塞隊列的類型,線程名稱的前綴等等,咱們能夠採用自定義線程池的方式來解決。spa
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) ;
corePoolSize:線程池大小,決定着新提交的任務是新開線程去執行仍是放到任務隊列中,也是線程池的最最核心的參數。通常線程池開始時是沒有線程的,只有當任務來了而且線程數量小於corePoolSize纔會建立線程。
maximumPoolSize:最大線程數,線程池能建立的最大線程數量。
keepAliveTime:在線程數量超過corePoolSize後,多餘空閒線程的最大存活時間。
unit:時間單位
workQueue:存放來不及處理的任務的隊列,是一個BlockingQueue。
threadFactory:生產線程的工廠類,能夠定義線程名,優先級等。
handler:拒絕策略,當任務來不及處理的時候,如何處理, 前面有講解。
其實這兩種方法的底層就是Runnable,Callable的實現。
多線程的一些基礎小知識,有興趣的同窗能夠園子裏翻翻其餘同窗的介紹,多線程、線程池、併發包這些東西不管是學習仍是面試都是比較重要的。
仔細檢查了須要優化的報表,發現由於這個報表的實時性要求比較高,同時涉及大量數據的計算操做,在優化了sql後效率仍是沒法達到滿意的程度,因此決定採用多線程的方式多個線程同時處理不一樣的業務邏輯,最後在合併數據返回,以達到提升效率的目的。
初步決定採用ExecutorService的submit方法,將一個複雜報表拆分爲四個子線程執行並返回結果。同時採用併發包中的CountDownLatch作同步器,等待 四個子線程執行完畢後,再在主線程進行數據合併操做。假如每一個子線程的執行時長在10分鐘左右,若是採用原先的串行方式的話,四個業務處理大概須要40分鐘左右,如今這種並行的方式執行只須要十分鐘的處理時間。
long startTime = DateUtils.getCurrentDateTime().getTime(); ExecutorService service = Executors.newFixedThreadPool(4); CountDownLatch latch = new CountDownLatch(4); Future<List<CapitalVO>> borrowIncrement = service.submit(new Callable<List<CapitalVO>>() { @Override public List<CapitalVO> call() throws Exception { List<CapitalVO> list = listBorrowIncrement(startDate, endDate); latch.countDown(); return list; } }); Future<List<OwnVO>> beceiveAccount = service.submit(new Callable<List<OwnVO>>() { @Override public List<OwnVO> call() throws Exception { List<OwnVO> list = listReceiveAccount(startDate, endDate); latch.countDown(); return list; } }); Future<List<OwnVO>> buaranteeAccount = service.submit(new Callable<List<OwnVO>>() { @Override public List<OwnVO> call() throws Exception { List<OwnVO> list = listGuaranteeAccount(startDate, endDate); latch.countDown(); return list; } }); Future<List<BorrowerVO>> borrowerRepayment = service.submit(new Callable<List<BorrowerVO>>() { @Override public List<BorrowerVO> call() throws Exception { List<BorrowerVO> list = listBorrowerRepayment(startDate, endDate); latch.countDown(); return list; } }); latch.await(); List<CapitalVO> borrowCapitalIncrement = borrowIncrement.get(); List<OwnVO> ownReceive = beceiveAccount.get(); List<OwnVO> ownAccountGuan = buaranteeAccount.get(); List<BorrowerVO> borrower = borrowerRepayment.get();
上述代碼利用CountDownLatch實現了線程同步,同時解決了本來串行執行時間較長的問題,在最終的效果上也是達到了預期的優化目標,比原報表的處理時長減小了四分之三的時間。
另外,有同窗提出如今是實現了四個線程並行處理,處理時長大概在十分鐘左右。可是假如其中一個線程出現了報錯,不在須要其餘線程繼續執行,這個時候該怎麼處理呢?
確實是存在這個狀況的,其實咱們能夠利用Future對象的 cancel(boolean mayInterruptIfRunning)來中斷其餘線程,底層其實仍是thread.interrupt()的方法實現。
總的來講技術方案上並無什麼特別的東西,可是有時候有沒有往這方面作就是一個思考的問題了。其實在工做中九成以上的人天天都是在作CRUD的業務,可是即使是CRUD每一個人作出來的東西仍是有所不一樣的。多思考多實踐,其實多線程並無那麼高不可攀,即使是簡單的報表,也是能夠作出不同的東西的。
最後,新年臨近,祝福你們新年快樂,也但願本身可以在新的一年作一個合格的creative worker。