我试图了解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池中的线程数量相同的数量&#39;
答案 0 :(得分:0)
要在此方案中查看async的优点,您需要使服务也异步。它不是做一个阻塞线程的Sleep,而是在三秒钟后完成调度运行后立即返回。在这种情况下,所有请求将在不到三秒的时间内完成;您的吞吐量为每秒133个请求,平均响应时间为3秒。如果您将线程数量调低,那么您的响应时间基本相同。
异步的要点是,等待I / O空闲的线程可以立即自由地执行其他操作,因此您不必使用尽可能多的线程(这是一种昂贵的资源)来满足您的需求。工作量。
答案 1 :(得分:0)
你的问题中有一个非常合成的情况。
让我们说你们两个都有10个线程(10个HTTP线程和5 + 5个线程用于异步版本),而你的程序不仅仅是调用一个休眠的方法。但是,80%的请求确实涉及3秒钟的操作(让我们说数据库查询)。
现在,在这两种情况下,您已经设法同时获得所有线程来调用阻塞方法。到目前为止,没有太大的区别。如果阻塞方法有另一个调用,则必须等待。
现在,突然你得到了一个不同的操作请求,让我们说登录。登录很简单,只检查数据库中的一行。在第一种情况下,它必须等待3秒,因为没有可用的HTTP线程来为它提供服务。在第二种情况下,您有一个完全不相关的线程池,但由于您没有使用它进行登录,您可以立即获得登录请求。
好的,那么为什么不在不使用DeferredResult
的情况下创建1000个大小的线程池呢?线程很贵。您不希望遇到这样的情况:您已经设法获得1000个执行某项昂贵任务的线程,您的CPU处于100%而不是3秒,每个请求的运行时间变为30秒。您的服务器阻塞和吞吐量变为0。
这也适用于连接池。少即是多。