最近在帶一些新員工作項目時發現有一些員工對JAVA線程的理解不夠深刻,出現一些比較低級的錯誤。所以產生寫這篇文章的想法,一來記錄一下遇到的問題和解決的方法另外本身也複習一下線程的用法。前端
需求1:項目中某個業務須要調用另外N個服務接口,而後根據返回的結果作篩選再返回給前端。java
固然最簡單的作法就是N個接口串行調用,可是若是每一個接口調用的時間都在1秒以上那麼N個接口調用完畢就須要耗費N秒,這在項目中是不可接受的。所以要求隊員要用多線程處理。那麼主線程要等待全部子線程任務執行完畢主要有如下幾種方法:服務器
方法1:使用CountDownLatch 這個類是在JDK1.5就已經提供了,它容許一個或多個線程一直等待,直到其餘線程的操做執行完後再執行。多線程
如下例子就是一個很經典的CountDownLatch的用法測試
public static void countDownLatchTest(){ long time = System.currentTimeMillis() ; final CountDownLatch countDownLatch = new CountDownLatch(5) ; for(int i=0;i<5;i++){ final int num = i ; new Thread(new Runnable() { public void run() { try { Thread.sleep(num*1000); } catch (InterruptedException e) { e.printStackTrace(); } /** * 使用CountDownLatch時要注意異常狀況,一旦沒處理好致使countDownLatch.countDown()沒執行會引發線程阻塞,致使CPU居高不下 if(num==3) System.out.println(Integer.parseInt("1.233")); **/ countDownLatch.countDown(); System.out.println(Thread.currentThread().getName()+"運行結束 運行時間爲:"+num +"秒 countDownLatch="+countDownLatch.getCount()); } }).start(); } try { countDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("總耗時==="+(System.currentTimeMillis()-time)); } public static void main(String[] args){ countDownLatchTest() ; }
輸出:spa
Thread-0運行結束 運行時間爲:0秒 countDownLatch=4 Thread-1運行結束 運行時間爲:1秒 countDownLatch=3 Thread-2運行結束 運行時間爲:2秒 countDownLatch=2 Thread-3運行結束 運行時間爲:3秒 countDownLatch=1 Thread-4運行結束 運行時間爲:4秒 countDownLatch=0 總耗時===4028
能夠看到最終耗時跟線程中最耗時的線程有關,可是使用CountDownLatch有必定風險,若是運行中沒有捕獲相關異常很容易致使CPU居高不下從而致使整個項目沒法運行(想測試的同窗能夠把countDownLatchTest中的註解打開),那麼遇到這種問題如何處理,固然把整個代碼try catch是一種解決方式。另一種比較優雅的解決方式是使用countDownLatch.await(5, TimeUnit.SECONDS) 代替countDownLatch.await(),方法中的那2個參數分別是超時時間和超時單位,若是線程在規定的時間內沒有處理完成則主線程被自動喚醒繼續執行下一步操做。線程
以上方法能夠實現對應功能可是有如下缺點:code
1.容易出錯,若是沒有捕獲異常或沒設置超時時間很容易形成服務器死機(*做者團隊就有隊員犯過這種錯誤,並且仍是3年以上的老員工)接口
2.沒有返回值,固然能夠用靜態變量存儲(不推薦)get
方法2:利用Thread.join方法
如下例子就是一個很經典的用法
public static void joinTest(){ long time = System.currentTimeMillis() ; Thread[] threads = new Thread[5] ; for(int i=1;i<=5;i++){ final int num = i ; threads[i-1] = new Thread(new Runnable() { public void run() { try { Thread.sleep(num*1000); System.out.println(Thread.currentThread().getName()+"耗時:"+num+"秒"); } catch (InterruptedException e) { e.printStackTrace(); } } }); threads[i-1].start(); } for(int i=0;i<threads.length;i++) { try { threads[i].join(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("總耗時==="+(System.currentTimeMillis()-time)); }
輸出結果:
Thread-0耗時:1秒 Thread-1耗時:2秒 Thread-2耗時:3秒 Thread-3耗時:4秒 Thread-4耗時:5秒 總耗時===5005
方法2的效果跟方法1的差很少,優缺點也同樣,因此就不在過多探討了
方法3:使用Future,利用Future.get()來實現需求
如下例子就是一個很經典的Future的用法
public static void futureTest(){ long time = System.currentTimeMillis() ; ExecutorService executorService = Executors.newFixedThreadPool(5) ; List<Future<String>> list = new ArrayList<Future<String>>() ; for(int i=1;i<=5;i++){ final int num = i ; list.add( executorService.submit(new Callable<String>() { public String call() throws Exception { Thread.sleep(num*1000); return Thread.currentThread()+": 耗時=="+num+" 秒"; } }) ); } for(Future<String> future:list){ try { System.out.println(future.get(5,TimeUnit.SECONDS)); } catch (Exception e) { e.printStackTrace(); } } System.out.println("總耗時==="+(System.currentTimeMillis()-time)); executorService.shutdownNow() ; } public static void main(String[] args){ futureTest() ; }
輸出:
Thread[pool-1-thread-1,5,main]: 耗時==1 秒 Thread[pool-1-thread-2,5,main]: 耗時==2 秒 Thread[pool-1-thread-3,5,main]: 耗時==3 秒 Thread[pool-1-thread-4,5,main]: 耗時==4 秒 Thread[pool-1-thread-5,5,main]: 耗時==5 秒 總耗時===5013
能夠看到3種效果是一致的。方法3相對方法1,方法2而言,代碼相對複雜一點,不過有返回值。無論採用那種方式都要注意設置超時時間,否則很容易引發系統崩潰。