一場由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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM