一場由CompletableFuture和ExecutorService引發的血案

現象

程序運行過程中無緣無故卡住,方法執行過程中停滯不前

解決

  1. 根據前段請求找出哪個方法卡住了,發現了方法m

  2. 本地debug發現每次進到m方法裏面就卡住

  3. 不給CompletableFuture傳入ExecutorService參數(默認是ForkJoinPool)就不會卡死,一度嚴重懷疑是我們ExecutorService配置出了問題

  4. m方法裏面大量使用了CompletableFuture以及注入的線程池

  5. 懷疑是線程耗盡,但是我們的隊列配置得比較大,卡住的時候觀察了隊列,還剩下很多位置,原則上會執行很慢,但是不會卡住

  6. 於是將線程池數量從16改爲200,果然成功執行,那麼說明確實是線程耗盡

  7. 配置spring線程池:spring.task.execution.pool.allow-core-thread-timeout=true,意思是:core線程在閒時也會銷燬

  8. 發現本地即使沒有請求,線程也一直不銷燬,基本上就是死鎖了

  9. 分析業務代碼,類似如下(類似下面代碼):

        @Test
        void test() throws Exception {
    		// 使用此就會卡住
            ExecutorService pool = Executors.newFixedThreadPool(1);
            // 使用此就不會
            ExecutorService pool = ForkJoinPool.commonPool();
    
            CompletableFuture<String> resp = CompletableFuture.supplyAsync(() -> {
                CompletableFuture<String> result = CompletableFuture.supplyAsync(() -> {
                    try {
                        SECONDS.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    return "dd";
                }, pool);
                try {
                    result.get();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return "ok";
            }, pool);
            resp.get();
        }
    
  10. 外層全部佔用線程池的線程,而裏面有需要等待內層的CompletableFuture返回結果,而內層又需要等待外層釋放線程

結果

  1. 死鎖造成的,ExecutorService配置的足夠大就不會出錯,但是這不治根
  2. 爲什麼forkjoinpool就不會出錯呢?forkjoinpool會源源不斷的創建線程

解決方案

不要嵌套使用CompletableFuture

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章