在使用Spring Boot
开发过程中,并发concurrency
问题不可避免。很多开发者存在这样的误区,使用Servlets
为每个请求分配一个新的线程进行处理就不再需要并发处理了。我将在这篇文章中介绍如何在Spring Boot
中处理并发并且如何避免一些问题。
Spring Boot 并发基础
有以下几点特别值得注意:
- 最大线程数:这是为处理应用程序的请求而分配的最大线程数。
- 共享资源:调用共享资源如数据库
- 异步方法调用:这些方法调用将会释放线程资源
- 内部共享资源:内部资源调用如缓存、共享应用程序状态
接下来我们逐一介绍如何处理这些场景
Spring Boot应用程序的最大线程数量
首先我们必须限制应用程序的线程数量。如果使用默认内嵌的Tomcat Server
,我们可以通过server.tomcat.max-threads
变量修改线程数量限制。默认为200
。我们可以通过修改此配置以更合理利用硬件资源。
共享外部资源
调用数据库或者第三方Restful
接口可能需要很长时间。
异步方法调用
我们可能遇到一个请求会调用多个服务。比如一次请求调用Service A、B、C
,你肯定不想这样调用:
Call service A -> Waiting response from Service A -> call service B -> Watiting ... -> Compose response from A B C
每个服务调用花费三秒,整个请求处理过程将会花费9秒。如果通过下面的做法肯定会更好。
Call service A
Call serviec B -> Waiting response from Service A B C -> compose reponse from A B C
Call serviec C
这样,显然我们只需要3秒响应。
异步和响应式微服务十分有趣,可以参考其他文章。这里我们只关注Spring boot
。
Spring Boot中异步调用
在Spring Boot
中使用注解@EnabelAsync
注解开启异步支持。使用@Async
将返回CompletableFuture<>
。这些异步方法将会在后台线程中执行。如果合理使用异步执行,可以避免等待时间。
共享内部资源
以上我们讨论了我们无法控制的外部资源,对于系统内部资源我们应该避免共享他们。Spring Service and Controller
都是单例模式,我们需要十分小心,当状态改变时,你需要立刻处理。共享状态的其他潜在来源是高速缓存和自定义服务器范围的组件(通常是监视,安全性等)。如果你必须使用共享状态资源,下面是我的建议:
- 处理不可变对象。如果对象是不可变的,则可以避免许多与并发相关的问题。如果你需要改变一些东西 - 只需创建一个新对象。
- 并非所有集合都是线程安全的。一个常见的陷阱是使用HashMap,假设它是线程安全的(它不是。如果你需要并发访问,请使用ConcurrentHashMap,HashTable或其他线程安全的解决方案。)。
- 不要假设第三方库是线程安全的。大多数代码都没有,并且必须控制对共享状态的访问。
- 如果你要依赖它 - 学习正确的并发性。我真的建议在实践中获得Java Concurrency的副本。写于2006年,但在2018年仍然非常相关。
总结
Spring中的并发和多线程是重要的主题。在本文中,我想强调在编写Spring Boot应用程序时需要注意的关键领域。如果您想在构建高要求,高质量的服务时取得成功,您需要围绕这一主题做出有意识的决策和权衡。我希望通过这篇文章你知道如何开始