線上生產環境dubbo報線程池滿了,經過一天排查鎖定在開三個線程計算最後合併數據的步驟中。簡單描述下該步驟線程開三個 調用三個不同的方法 使用countdownlatch 計數器等待三個方法全部執行完成 合併數據。但是由於其中一個方法調用第三方接口,接口返回異常導致轉換數據報錯。導致其中一個方法未正常完成。
舉例demo:
public static void main(String[] args) { ExecutorService executorService =Executors.newFixedThreadPool(3); CountDownLatch cdl = new CountDownLatch(3); executorService.execute(new Runnable() { @Override public void run() { /*try { function1(); } catch (Exception e) { //異常處理 e.printStackTrace(); } finally { cdl.countDown(); }*/ function1(); } }); executorService.execute(new Runnable() { @Override public void run() { function2(); cdl.countDown(); } }); executorService.execute(new Runnable() { @Override public void run() { function3(); cdl.countDown(); } }); try { cdl.await(); //cdl.await(20,TimeUnit.SECONDS); System.out.println("三個執行線程結束"); } catch (InterruptedException e) { e.printStackTrace(); System.out.println("執行線程異常"); } finally { executorService.shutdown(); System.out.println("執行線程關閉"); } } private static void function1(){ int i = 10/0; System.out.println("方法一"); } private static void function2(){ System.out.println("方法二"); } private static void function3(){ System.out.println("方法三"); }
方法一拋出異常,但是沒有做異常處理導致不會執行線程關閉步驟,是不是和想象中不一樣,一開始我也是懵,看了一下CountDownLatch原理就很好理解了,
“CountDownLatch是通過一個計數器來實現的,計數器的初始化值爲線程的數量。每當一個線程完成了自己的任務後,計數器的值就相應得減1。當計數器到達0時,表示所有的線程都已完成任務,然後在閉鎖上等待的線程就可以恢復執行任務。”【1】
舉一個現實中例子就是:CountDownLatch 就像跑步比賽中的裁判,三個方法就是就是三位運動員,運動員2,3都已經到達終點,但是運動員1摔倒了,動不了。裁判員只看到兩位運動員到達終點不能宣佈比賽結束,所以一直等。。。
就像這樣的場景導致線上service執行線程阻塞,接口調用次數累計導致dubbo線程滿了(跟dubbo線程模型有關,有時間具體談談這一點)
知道原因了,就要考慮怎麼修改
比賽不能無限期等,所以比賽必須在有限時間內結束,所以使用
public boolean await(long timeout, TimeUnit unit) throws InterruptedException { return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout)); }
線程內部也許要增加異常處理
executorService.execute(new Runnable() { @Override public void run() { try { function1(); } catch (Exception e) { //異常處理 e.printStackTrace(); } finally { cdl.countDown(); } // function1(); } });
修改後demo
public static void main(String[] args) { ExecutorService executorService =Executors.newFixedThreadPool(3); CountDownLatch cdl = new CountDownLatch(3); executorService.execute(new Runnable() { @Override public void run() { try { function1(); } catch (Exception e) { //異常處理 e.printStackTrace(); } finally { cdl.countDown(); } // function1(); } }); executorService.execute(new Runnable() { @Override public void run() { function2(); cdl.countDown(); } }); executorService.execute(new Runnable() { @Override public void run() { function3(); cdl.countDown(); } }); try { // cdl.await(); cdl.await(20,TimeUnit.SECONDS); System.out.println("三個執行線程結束"); } catch (InterruptedException e) { e.printStackTrace(); System.out.println("執行線程異常"); } finally { executorService.shutdown(); System.out.println("執行線程關閉"); } } private static void function1(){ int i = 10/0; System.out.println("方法一"); } private static void function2(){ System.out.println("方法二"); } private static void function3(){ System.out.println("方法三"); }
執行結果
大家結合自己的現實使用修改,爬過了使用坑,記錄下分享下 ,希望能對別人有用
【1】參考https://www.jianshu.com/p/4b6fbdf5a08f