在使用Spring Boot开发过程中,并发concurrency问题不可避免。很多开发者存在这样的误区,使用Servlets为每个请求分配一个新的线程进行处理就不再需要并发处理了。我将在这篇文章中介绍如何在Spring Boot中处理并发并且如何避免一些问题。

Spring Boot 并发基础

有以下几点特别值得注意:

  1. 最大线程数:这是为处理应用程序的请求而分配的最大线程数。
  2. 共享资源:调用共享资源如数据库
  3. 异步方法调用:这些方法调用将会释放线程资源
  4. 内部共享资源:内部资源调用如缓存、共享应用程序状态

接下来我们逐一介绍如何处理这些场景

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都是单例模式,我们需要十分小心,当状态改变时,你需要立刻处理。共享状态的其他潜在来源是高速缓存和自定义服务器范围的组件(通常是监视,安全性等)。如果你必须使用共享状态资源,下面是我的建议:

  1. 处理不可变对象。如果对象是不可变的,则可以避免许多与并发相关的问题。如果你需要改变一些东西 - 只需创建一个新对象。
  2. 并非所有集合都是线程安全的。一个常见的陷阱是使用HashMap,假设它是线程安全的(它不是。如果你需要并发访问,请使用ConcurrentHashMap,HashTable或其他线程安全的解决方案。)。
  3. 不要假设第三方库是线程安全的。大多数代码都没有,并且必须控制对共享状态的访问。
  4. 如果你要依赖它 - 学习正确的并发性。我真的建议在实践中获得Java Concurrency的副本。写于2006年,但在2018年仍然非常相关。

总结

Spring中的并发和多线程是重要的主题。在本文中,我想强调在编写Spring Boot应用程序时需要注意的关键领域。如果您想在构建高要求,高质量的服务时取得成功,您需要围绕这一主题做出有意识的决策和权衡。我希望通过这篇文章你知道如何开始