Spring MVC 中是怎样保证Controller的并发安全?

打印 上一主题 下一主题

主题 978|帖子 978|积分 2934

在 Spring MVC 中,默认情况下,@Controller 是单例的,这意味着所有请求共享一个 Controller 实例。在并发请求的情况下,多个线程会同时访问这个控制器实例。为确保并发安全,Spring 并不会自动对 Controller 举行线程安全保护,而是通过框架计划、最佳实践,以及开发者的代码编写方式来保证安全性。以下是 Spring MVC 保证 Controller 并发安全的方式和开发者应遵照的最佳实践。
1. 无状态计划的控制器

在 Spring MVC 中,单例 Controller 主要依靠于无状态计划来实现线程安全。无状态计划是控告制器中不包含任何可变的实例变量,因此所有请求在访问 Controller 时不会共享状态。
示例:无状态控制器

  1. import org.springframework.stereotype.Controller;
  2. import org.springframework.web.bind.annotation.GetMapping;
  3. import org.springframework.web.bind.annotation.RequestParam;
  4. import org.springframework.web.bind.annotation.ResponseBody;
  5. @Controller
  6. public class SafeController {
  7.     @GetMapping("/safe")
  8.     @ResponseBody
  9.     public String handleRequest(@RequestParam("input") String input) {
  10.         // 使用局部变量,不存在线程安全问题
  11.         String result = "Processed: " + input;
  12.         return result;
  13.     }
  14. }
复制代码
说明:在这个例子中,result 是局部变量,每个请求都有自己的局部变量空间,因此线程之间不会相互影响,从而保证了线程安全。
2. 禁止利用共享的可变实例变量

Spring MVC 中的控制器默认是单例的,因此任何可变的实例变量会在并发访问时导致线程安全题目。为此,应避免在控制器中利用任何可变的实例变量,特别是像 List、Map 等集合类型。
示例:避免利用共享的可变实例变量

  1. import org.springframework.stereotype.Controller;
  2. import org.springframework.web.bind.annotation.GetMapping;
  3. import org.springframework.web.bind.annotation.ResponseBody;
  4. @Controller
  5. public class UnsafeController {
  6.     private int counter = 0;  // 非线程安全的实例变量
  7.     @GetMapping("/unsafe")
  8.     @ResponseBody
  9.     public String handleRequest() {
  10.         counter++;  // 非线程安全操作
  11.         return "Counter: " + counter;
  12.     }
  13. }
复制代码
在上面的例子中,counter 是一个实例变量,会被多个请求共享访问。这种情况下,如果有多个线程同时访问 handleRequest,大概会导致 counter 的值出现不同等。因此,避免利用可变实例变量是保证线程安全的核心之一。
3. 利用 ThreadLocal 共享数据

如果确实需要在多个方法间共享一些数据,可以利用 ThreadLocal,它能够为每个线程提供独立的变量副本,使数据在线程之间相互隔离,避免并发冲突。
示例:利用 ThreadLocal 实现线程安全的共享数据

  1. import org.springframework.stereotype.Controller;
  2. import org.springframework.web.bind.annotation.GetMapping;
  3. import org.springframework.web.bind.annotation.RequestParam;
  4. import org.springframework.web.bind.annotation.ResponseBody;
  5. @Controller
  6. public class ThreadLocalController {
  7.     private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();
  8.     @GetMapping("/threadlocal")
  9.     @ResponseBody
  10.     public String handleRequest(@RequestParam("input") String input) {
  11.         threadLocal.set(input);  // 每个线程独立的 threadLocal 副本
  12.         try {
  13.             return processInput();
  14.         } finally {
  15.             threadLocal.remove();  // 避免内存泄漏
  16.         }
  17.     }
  18.     private String processInput() {
  19.         return "Processed: " + threadLocal.get();
  20.     }
  21. }
复制代码
说明


  • ThreadLocal 为每个线程提供独立的变量副本,使每个请求的数据相互独立。
  • 留意在方法调用竣过后调用 threadLocal.remove() 清理数据,以防止内存泄漏。
4. 利用局部变量存储临时数据

局部变量是在方法栈上分配的,线程私有,天然是线程安全的。因此,将方法内的中间状态或临时数据存储在局部变量中可以保证线程安全。
示例:利用局部变量存储中间状态

  1. import org.springframework.stereotype.Controller;
  2. import org.springframework.web.bind.annotation.GetMapping;
  3. import org.springframework.web.bind.annotation.RequestParam;
  4. import org.springframework.web.bind.annotation.ResponseBody;
  5. @Controller
  6. public class LocalVariableController {
  7.     @GetMapping("/localvar")
  8.     @ResponseBody
  9.     public String handleRequest(@RequestParam("input") String input) {
  10.         // 使用局部变量存储临时状态,避免实例变量共享
  11.         String result = "Processed: " + input;
  12.         return result;
  13.     }
  14. }
复制代码
说明


  • result 是局部变量,每个请求都会有自己的 result,因此纵然在并发情况下也是线程安全的。
5. 利用 @Scope("prototype") 使控制器成为多例(不推荐)

固然可以通过 @Scope("prototype") 将控制器作用域设置为多例,每次请求都会创建一个新的控制器实例,避免了线程安全题目,但不推荐这样做,因为它会增加内存和对象创建的开销。
示例:将控制器设为多例

  1. import org.springframework.context.annotation.Scope;
  2. import org.springframework.stereotype.Controller;
  3. import org.springframework.web.bind.annotation.GetMapping;
  4. import org.springframework.web.bind.annotation.ResponseBody;
  5. @Controller
  6. @Scope("prototype")  // 设置为多例模式
  7. public class PrototypeController {
  8.     private int count = 0;
  9.     @GetMapping("/prototype")
  10.     @ResponseBody
  11.     public String handleRequest() {
  12.         count++;
  13.         return "Request count: " + count;
  14.     }
  15. }
复制代码
说明


  • 每次请求都会创建一个新的 PrototypeController 实例,count 不会被共享,因此是线程安全的。
  • 但这种做法会增加对象创建的开销和内存利用,因此不推荐在高并发情况下利用。
总结

Spring MVC 保证 Controller 的并发安全主要依靠以下原则和实践:

  • 单例无状态计划:@Controller 默认是单例,因此控制器应计划为无状态。
  • 避免利用共享的可变实例变量:控制器中不应包含任何共享的可变实例变量,以免在并发访问时发生线程安全题目。
  • 利用 ThreadLocal 存储线程独立的临时状态:当需要共享一些临时状态时,利用 ThreadLocal 来隔离数据。
  • 利用局部变量存储临时数据:将中间状态或临时数据存储在局部变量中,以确保每个请求的隔离性和线程安全。
通过这些计划原则和代码实践,Spring MVC 的 Controller 能够在高并发环境中有用保证线程安全。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

半亩花草

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表