1. 概述
本文將探討警告:“在非阻塞上下文中可能阻塞調用可能導致線程飢餓” 。首先,我們將通過一個簡單的示例重現此警告,並探索如何在我們的案例中抑制它。
然後,我們將討論忽略此警告的風險,並探索兩種有效解決問題的方法。
2. 非阻塞上下文中的阻塞方法
IntelliJ IDEA 在我們嘗試在反應式上下文中執行阻塞操作時,會發出“可能在非阻塞上下文中執行阻塞調用可能導致線程飢餓”警告。
假設我們正在使用 Spring WebFlux 與 Netty 服務器構建一個反應式 Web 應用程序。如果在處理應保持非阻塞的 HTTP 請求時引入阻塞操作,我們將會遇到此警告:
此警告來自 IntelliJ IDEA 的靜態分析。如果對應用程序的影響不大,我們可以輕鬆地使用“BlockingMethodInNonBlockingContext”檢查名來抑制此警告。
@SuppressWarnings("BlockingMethodInNonBlockingContext")
@GetMapping("/warning")
Mono<String> warning() {
// ...
}然而,至關重要的是要理解問題的根源並評估其影響。 在某些情況下,這可能導致阻塞負責處理 HTTP 請求的線程,從而產生嚴重後果。
3. 理解警告
讓我們展示一個忽略此警告可能導致線程飢餓並阻止傳入 HTTP 流量的場景。對於此示例,我們將添加另一個端點,並使用 <em >Thread.sleep()</em> 故意讓線程阻塞兩秒,儘管它處於反應式上下文中:
@GetMapping("/blocking")
Mono<String> getBlocking() {
return Mono.fromCallable(() -> {
try {
Thread.sleep(2_000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return "foo";
});
}在這種情況下,Netty的事件循環線程處理傳入的HTTP請求可能會迅速被阻塞,導致響應不及時。 例如,如果我們發送兩百個併發請求,應用程序響應所有請求需要32秒,儘管沒有涉及任何計算。此外,這也會影響其他端點——即使它們不需要阻塞操作。
這種延遲是由於Netty HTTP線程池的大小為十二,因此它只能同時處理十二個請求造成的。如果我們在IntelliJ Profiler中檢查,我們可以看到線程大部分時間被阻塞,並且測試過程中CPU使用率非常低:
4. 解決問題
理想情況下,我們應該切換到響應式 API 以解決此問題。 但是,如果無法實現,我們應該為此類操作使用單獨的線程池,以避免阻塞 HTTP 線程。
4.1. 使用反應式替代方案
首先,我們應儘可能採用反應式方法。這意味着尋找替代阻塞操作的反應式方案。
例如,我們可以嘗試使用與 Spring Data Reactive 倉庫或 WebClient 這樣的反應式 HTTP 客户端結合的反應式數據庫驅動程序。 在我們簡單的案例中,我們可以使用 Mono 的 API 來延遲響應兩秒,而不是依賴阻塞的 Thread.sleep():
@GetMapping("/non-blocking")
Mono<String> getNonBlocking() {
return Mono.just("bar")
.delayElement(Duration.ofSeconds(2));
}採用這種方法,應用程序可以處理數百個併發請求,並在我們引入的2秒延遲後發送所有響應。
4.2. 使用專用調度器處理阻塞操作
另一方面,在某些情況下,我們無法使用反應式 API。 常見的情況是使用非反應式驅動程序查詢數據庫,這會導致阻塞操作:
@GetMapping("/blocking-with-scheduler")
Mono<String> getBlockingWithDedicatedScheduler() {
String data = fetchDataBlocking();
return Mono.just("retrieved data: " + data);
}在這些情況下,我們可以將阻塞操作包裝在一個 Mono 中,並使用 subscribeOn() 來指定其執行的調度器。 這將提供一個 Mono<String>,該 Mono 隨後可以映射到我們所需的響應格式:
@GetMapping("/blocking-with-scheduler")
Mono<String> getBlockingWithDedicatedScheduler() {
return Mono.fromCallable(this::fetchDataBlocking)
.subscribeOn(Schedulers.boundedElastic())
.map(data -> "retrieved data: " + data);
}5. 結論
在本教程中,我們探討了 IntelliJ 靜態分析器生成的 “在非阻塞上下文中可能導致線程飢餓” 警告。通過代碼示例,我們展示了忽略此警告如何會阻止 Netty 的線程池處理傳入的 HTTP 請求,導致應用程序無響應。
隨後,我們瞭解到儘可能採用反應式 API 可以幫助我們解決此問題。此外,我們還學習到,在沒有反應式替代方案的情況下,應使用單獨的線程池來處理阻塞操作。