Spring的bean默认都是单例的。在某些情况下,单例对于并发来说是不安全的。以控制器为例。问题的根源在于我们可能在Controller中定义了成员变量。这样,多个请求过来时,进入的是同一个单例的controller对象,修改了这个成员变量的值,所以会相互影响,达不到并发安全的效果(区别于概念线程隔离,后面会解释)。1.抛出问题首先举个例子来证明单例的并发不安全:@ControllerpublicclassHomeController{privateinti;@GetMapping("testsingleton1")@ResponseBodypublicinttest1(){return++i;多次访问这个url,可以看到每次的结果都是自增的,这样的代码显然并发不安全。2、解决方案因此,为了不影响无状态的海量Http请求,我们可以采取以下措施:2.1单例原型对于web项目,可以在Controller类中添加注解@Scope("prototype")或@Scope("request"),对于非web项目,在Component类中添加注解@Scope("prototype")。优点:实现简单;缺点:大大增加了bean创建、实例化和销毁的服务器资源开销。2.2线程隔离类ThreadLocal有人想到了线程隔离类ThreadLocal。为了实现并发安全,我们尝试将成员变量包装成ThreadLocal,同时打印出Http请求的线程名。修改代码如下:@ControllerpublicclassHomeController{privateThreadLocali=newThreadLocal<>();@GetMapping("testsingleton1")@ResponseBodypublicinttest1(){if(i.get()==null){i.set(0);}i.set(i.get().intValue()+1);log.info("{}->{}",Thread.currentThread().getName(),i.get());返回i.get().intValue();}}推荐一个SpringBoot基础实战教程:https://github.com/javastacks...多次访问这个url进行测试,打印日志如下:[INFO]2019-12-0311:49:08,226com.cjia.ds.controller.HomeController.test1(HomeController.java:50)http-nio-8080-exec-1->1[信息]2019-12-0311:49:16,457com.cjia.ds。controller.HomeController.test1(HomeController.java:50)http-nio-8080-exec-2->1[信息]2019-12-0311:49:17,858com.cjia.ds.controller.HomeController.test1(HomeController.java:50)http-nio-8080-exec-3->1[信息]2019-12-0311:49:18,461com.cjia.ds.controller.HomeController.test1(HomeController.java:50)http-nio-8080-exec-4->1[信息]2019-12-0311:49:18,974com.cjia.ds.controller.HomeController.test1(HomeController.java:50)http-nio-8080-exec-5->1[信息]2019-12-0311:49:19,696com.cjia.ds.controller.HomeController.test1(HomeController.java:50)http-nio-8080-exec-6->1[信息]2019-12-0311:49:22,138com。cjia.ds.controller.HomeController.test1(HomeController.java:50)http-nio-8080-exec-7->1[信息]2019-12-0311:49:22,869com.cjia.ds.controller.HomeController.test1(HomeController.java:50)http-nio-8080-exec-9->1[信息]2019-12-0311:49:23,617com.cjia.ds.controller.HomeController.test1(HomeController.java:50)http-nio-8080-exec-8->1[信息]2019-12-0311:49:24,569com.cjia.ds.controller.HomeController.test1(HomeController.java:50)http-nio-8080-exec-10->1[信息]2019-12-0311:49:25,218com.cjia.ds.controller.HomeController.test1(HomeController.java:50)http-nio-8080-exec-1->2[信息]2019-12-0311:49:25,740com.cjia.ds.controller.HomeController.test1(HomeController.java:50)http-nio-8080-exec-2->2[信息]2019-12-0311:49:43,308com.cjia.ds.controller.HomeController.test1(HomeController.java:50)http-nio-8080-exec-3->2[信息]2019-12-0311:49:44,420com.cjia.ds.controller.HomeController.test1(HomeController.java:50)http-nio-8080-exec-4->2[信息]2019-12-0311:49:45,271com.cjia.ds.controller.HomeController.test1(HomeController.java:50)http-nio-8080-exec-5->2[信息]2019-12-0311:49:45,808com.cjia.ds.controller.HomeController.test1(HomeController.java:50)http-nio-8080-exec-6->2[信息]2019-12-0311:49:46,272com.cjia.ds.controller.HomeController.test1(HomeController.java:50)http-nio-8080-exec-7->2[信息]2019-12-0311:49:46,489com.cjia.ds.controller.HomeController.test1(HomeController.java:50)http-nio-8080-exec-9->2[我NFO]2019-12-0311:49:46,660com.cjia.ds.controller.HomeController.test1(HomeController.java:50)http-nio-8080-exec-8->2[信息]2019-12-0311:49:46,820com.cjia.ds.controller.HomeController.test1(HomeController.java:50)http-nio-8080-exec-10->2[信息]2019-12-0311:49:46,990com。cjia.ds.controller.HomeController.test1(HomeController.java:50)http-nio-8080-exec-1->3[信息]2019-12-0311:49:47,163com.cjia.ds.controller.HomeController.test1(HomeController.java:50)http-nio-8080-exec-2->3...从日志分析,连续20多次请求的结果是1,2,3等,我们期望无论我有多少个并发请求,结果每次都是1;同时我们可以发现web服务器默认的请求线程池大小是10,而这10个核心线程可以被后面不同的Http请求复用,所以这也是为什么同一个线程的结果名称不再重复。总结:ThreadLocal方法可以实现线程隔离,但是仍然无法实现并发安全。2.3尽量避免使用成员变量有人说单例bean的成员变量好麻烦,不使用成员变量就尽量避免使用。如果业务允许,在RequestMapping方法中用局部变量替换成员变量,这样就省事了。这种方法自然是最合适的,也是我最推荐的。代码修改如下:@ControllerpublicclassHomeController{@GetMapping("testsingleton1")@ResponseBodypublicinttest1(){inti=0;//TODO商业代码return++i;但是在少数情况下,必须要用到成员变量,应该怎么处理呢?2.4使用具有丰富API的并发安全的类Java编程语言。如果非要在单例bean中使用成员变量,可以考虑使用并发安全的容器,比如ConcurrentHashMap、ConcurrentHashSet等,组合我们的成员变量(一般是当前运行的任务列表这样的变量)即可打包在这些并发安全的容器中进行管理。2.5分布式或微服务的并发安全如果进一步考虑微服务或分布式服务的影响,方法4不足以应对,那么可以使用分布式缓存中间件,可以共享某些信息,比如Redis等,所以同一服务的不同服务实例可以拥有相同的共享信息(例如当前运行的任务列表和其他变量)。3、springbean以下5个作用域的补充说明:singleton:单例模式,spring创建applicationContext容器时,spring会初始化该作用域的所有实例,lazy-init可以避免预处理;prototype:原型模式,每次通过getBean获取bean,都会生成一个新的实例,创建后spring不再管理;(以下仅在web项目中使用)request:搞web的大家应该都明白request域,也就是每次请求都会生成一个新的实例,和prototype的区别是创建之后,接下来管理,春还在听;session:每次session,同上;globalsession:全局的web域,类似于servlet中的application。版权声明:本文为CSDN博主“DayDayUp”原创文章,遵循CC4.0BY-SA版权协议,转载请附上原文出处链接及本声明。