[英]Java - Async - Thread pool
我試圖了解java中async的好處。
場景1:我有一個部署到tomcat的spring boot web app,tomcat min和max threads都設置為200。
@Service
public class MyService{
public String execute(){
try {
//simulate blocking
Thread.sleep(3000);
} catch (InterruptedException e) {}
return "OK";
}
}
@RestController
public class MyController {
@Autowired
private MyService service;
@RequestMapping("/test")
public String test(){
return service.execute();
}
}
場景2:我有一個部署到tomcat的spring boot web app,tomcat min和max threads都設置為100
@Service
public class MyService{
public String execute(){
try {
//simulate blocking
Thread.sleep(3000);
} catch (InterruptedException e) {}
return "OK";
}
}
@RestController
public class MyController {
@Autowired
private MyService service;
private ExecutorService executorService = Executors.newFixedThreadPool(100);
@RequestMapping("/test")
public DeferredResult<String> test(){
DeferredResult<String> deferredResult = new DeferredResult<>();
CompletableFuture.supplyAsync(service::execute, executorService).
whenCompleteAsync((result, throwable) -> deferredResult.setResult(result));
return deferredResult;
}
}
在每種方案中,線程總數為200。
但我不知道方案2將如何表現更好:
在方案1中,如果400個請求同時進入,則前200個將由200個http線程提供服務,接下來的200個將需要等待3秒(加一點),直到其中一個線程再次可用。
因此吞吐量是每6秒400個請求=每秒66.6個請求。
平均響應時間為(200 * 3 + 200 * 6)/(400)= 4.5秒
在方案2中,如果400個請求同時進入。 前100個將由100個http線程立即提供,這些線程中的每一個都將調用該服務,而不是等待結果,然后立即恢復,並可用於服務接下來的100個請求。 但是現在對於第二個100個請求,當每個http線程調用該服務時,該服務當前正在等待3秒(減去一點)以完成處理前100個線程。 所以下一個100排隊(在executorservice的線程池中)。 因此,幾乎沒有時間,我們已經處理了所有400個請求,但是在服務中處理了100個請求(等待3秒),而300個在執行服務線程池中排隊。 3秒后,前100個完成,接下來100個出列並處理,依此類推。
因此吞吐量在12秒內是400個請求=每秒33.3個請求
平均響應時間為(100 * 3 + 100 * 6 + 100 * 9 + 100 * 12)/(400)= 7.5秒
現在,有人可能會爭辯說,'我可以通過增加執行程序服務線程池中的線程數來改進方案2',我可以回復,'好吧,那么我可以增加方案1中tomcat池中的線程數相同金額'
要在此方案中查看async的優點,您需要使服務也異步。 它不是做一個阻塞線程的Sleep,而是在三秒鍾后完成調度運行后立即返回。 在這種情況下,所有請求將在不到三秒的時間內完成; 您的吞吐量為每秒133個請求,平均響應時間為3秒。 如果您將線程數量調低,那么您的響應時間基本相同。
異步的要點是,等待I / O空閑的線程可以立即自由地執行其他操作,因此您不必使用盡可能多的線程(這是一種昂貴的資源)來滿足您的工作負載。
你的問題中有一個非常合成的情況。
假設你有兩個線程(10個HTTP線程和5 + 5個線程用於異步版本),你的程序不僅僅是調用一個睡眠的方法。 但是,80%的請求確實涉及3秒鍾的操作(假設數據庫查詢)。
現在,在這兩種情況下,您已經設法同時獲得所有線程來調用阻塞方法。 到目前為止,沒有太大的區別。 如果阻塞方法有另一個調用,則必須等待。
現在,突然你得到了一個不同的操作請求,讓我們說登錄。 登錄很簡單,只檢查數據庫中的一行。 在第一種情況下,它必須等待3秒,因為沒有可用的HTTP線程來為它提供服務。 在第二種情況下,您有一個完全無關的線程池,但由於您沒有使用它進行登錄,因此您可以立即為您的登錄請求提供服務。
好的,那么為什么不在不使用DeferredResult
情況下創建1000大小的線程池呢? 線程很貴。 您不希望遇到這樣的情況:您已經設法獲得1000個執行某些昂貴任務的線程,您的CPU處於100%而不是3秒,每個請求的運行時間變為30秒。 您的服務器阻塞和吞吐量變為0。
這也適用於連接池。 少即是多。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.