原文:http://blog.kail.xyz/post/2019-04-21/tools/httpclient-lock.html
最近遇到一個使用 Apache HttpClient 過程中的問題,具體場景是
- 通過 Spring
@Scheduled(cron = "..")
方式執行定時任務 - 定時任務中並發使用 HttpClient 拉取數據
- 但是定時任務只會執行一次
- 因為 Spring 基於注解的定時任務,在非異步的情況的,上一次任務執行完之前不會執行下一個任務
- 所以懷疑是第一次執行的任務一直沒有執行完,卡在了某個地方
還原場景
maven 依賴
1 |
<dependency> |
程序簡化后,代碼如下
1 |
package xyz.kail.demo.java.se.temp; |
正常情況下,以上程序會輸出
countDownLatch.await();
httpClient.close();
executorService.shutdown();
但是多運行幾次,會發現有時候會只輸出 countDownLatch.await();
,程序會卡在 httpClient.close();
查看線程信息
1 |
$ jcmd |
回憶一下線程狀態
1 |
package java.lang; |
根據 Thread.print 信息找到源碼位置
AbstractConnPool.java:377
1 |
// 入口 |
AbstractConnPool.java:207
1 |
|
可能原因分析
根據調用入口 大致可以確定 是 close 釋放 HttpClient 資源的時候 和 execute 請求獲取資源的時候 產生了死鎖。
模擬可能的執行流程如下:
- 線程1:condition.signalAll()
- 線程2: 獲取 this 鎖
- 線程1:獲取 this 鎖 失敗,BLOCKED
- 線程2:執行 getPoolEntryBlocking 方法
- 線程2:condition.wait (WAITING (parking))
- 最終兩個線程 在互相等待對方釋放鎖/喚醒,產生死鎖
分析到這基本上可以確定 應該是 httpcore 中 org.apache.http.pool.AbstractConnPool
這個類的Bug
如何解決
如果是 HttpClient (httpcore 模塊) 的 Bug,可以看一下官方有沒有修復,到 Github 官方倉庫 httpcomponents-core 找到指定的文件 org/apache/http/pool/AbstractConnPool.java 查看 提交歷史, Ctrl + F 搜索 關鍵字 fix
,最終找到了這次提交 HTTPCORE-446: fixed deadlock in AbstractConnPool shutdown code
點擊這次提交 右側的 <>
按鈕(Browse the repository at this point in the history) 查看這次提交后的 git 倉庫,發現修復之后 httpcore 的版本是 4.4.7-SNAPSHOT
。
升級到 httpcore maven 版本到 4.4.7+
后重試最初的代碼,發現死鎖問題已經解決,但是會拋出以下異常:
1 |
org.apache.http.impl.execchain.RequestAbortedException: Request aborted |
如何避免
- 多線程並發使用共享資源的時候,如果不了解共享資源的內部機制,不了解是否存在並發問題的時候,一定要小心,如果不分析源碼,最好也上網查一下相關的問題,如:”httpclient 並發問題” 等
- CountDownLatch 的使用方式也存在問題,比如這個示例程序中,countDownLatch.countDown() 寫在了線程執行邏輯的第一行,真正的邏輯還沒開始執行,就已經 countDown,實際上並沒有起到相應的作用
- 如果確定共享資源存在並發問題,並且不確定官方有沒有提供相應的解決方案的話,最快但不是最好的方式是:把共享資源放到線程內作為線程內部的資源,避免並發問題
- …
其它收獲
- http-core 與 httpclient/httpmime 是分開兩個倉庫維護的,所以 maven 版本號不一定一致,但是 httpclient/httpmime 是同一個倉庫下的兩個模塊,理論上版本號應該是一致的
- 使用httpclient必須知道的參數設置及代碼寫法、存在的風險
- 必須設置超時時間,否則可能在 網絡 IO 上 卡死
- 默認重試3次機制
- 連接池管理
- 關於HttpClient重試策略的研究
- Apache HttpClient 資源釋放、請求超時,導致線程池用光、內存不足
- HttpClient多線程並發問題