当多个用户同时请求一个服务时,容器会给每一个请求分配一个线程,这时多个线程会并发执行该请求对应的业务逻辑,如果业务逻辑有对单例状态的修改(体现为此单例的成员属性),则必须考虑线程安全问题。
在Spring中无状态的Bean适合用单例模式,这样可以共享实例提高性能。有状态的Bean在多线程环境下不安全,一般用Prototype
模式或者使用ThreadLocal
解决线程安全问题
Spring的Bean默认都是单例的,某些情况下,单例是并发不安全的。
以 Controller
举例,假如我们在 Controller
中定义了成员变量。当多个请求来临,进入的都是同一个单例的 Controller
对象,并对此成员变量的值进行修改操作,因此会互相影响,会有并发安全的问题。
应该怎么解决呢?
为了让多个HTTP请求之间不互相影响,可以采取以下措施:
1、单例变原型
对 web 项目,可以 Controller
类上加注解 @Scope("prototype")
或 @Scope("request")
,对非 web 项目,在 Component
类上添加注解 @Scope("prototype")
。
这种方式实现起来非常简单,但是很大程度上增大了 Bean 创建实例化销毁的服务器资源开销。
2、尽量避免使用成员变量
在业务允许的条件下,可以将成员变量替换为方法中的局部变量。这种方式个人认为是最恰当的。
3、使用并发安全的类
如果非要在单例Bean中使用成员变量,可以考虑使用并发安全的容器,如 ConcurrentHashMap
、ConcurrentHashSet
等等,将我们的成员变量包装到这些并发安全的容器中进行管理即可。
4、分布式或微服务的并发安全
如果还要进一步考虑到微服务或分布式服务的影响,方式3便不合适了。这种情况下可以借助于可以共享某些信息的分布式缓存中间件,如Redis等。这样即可保证同一种服务的不同服务实例都拥有同一份共享信息了。
idea 在我们经常使用的@Autowired
注解上添加了警告。警告内容是: Field injection is not recommended
, 译为: 不推荐使用属性注入。
Spring常用的注入方式有:属性注入, 构造方法注入, set 方法注入
其中,基于属性注入的方式,容易导致Spring 初始化失败。因为在Spring在初始化的时候,可能由于属性在被注入前就引用而导致空指针异常,进而导致容器初始化失败。
如果可能的话,尽量使用构造器注入。Lombok提供了一个注解@RequiredArgsConstructor
, 可以方便我们快速进行构造注入。
@Autowired是属性注入,而且@Autowired默认是按照类型匹配(ByType),因此有可能会出现两个相同的类型bean,进而导致Spring 装配失败。
如果要使用属性注入的话,可以使用 @Resource
代替 @Autowired
注解。@Resource默认是按照名称匹配(ByName),如果找不到则是ByType注入。另外,@Autowired是Spring提供的,@Resource是JSR-250提供的,是Java标准,我们使用的IoC容器会去兼容它,这样即使更换容器,也可以正常工作。