1. Spring 框架中的 Bean 是单例的吗
在回答 Spring 框架中的单例 Bean 是线程安全的吗
这个问题之前,我们先来思考一个问题,Spring 框架中的 Bean 是单例的吗
默认情况下,Spring 框架中的 Bean 确实是单例的,我们可以通过 @Scope 注解来设置 Bean 是不是单例的
- singleton:同一个类型的 Bean 在 Spring IOC 容器中只有一个实例
- prototype:同一个类型的 Bean 在 Spring IOC 容器中可以有多个实例
2. Spring 框架中的单例 Bean 是线程安全的吗
我们先说结论,Spring 框架中的单例 Bean 不是线程安全的
既然 Spring 框架中的单例 Bean 不是线程安全的,那我们在开发过程中使用这个单例 Bean 会不会有什么问题呢
我们来看以下例子
如果有大量的请求同时到达 UserController,getById 方法里面的代码会不会出现线程安全问题呢
我们知道,count 是 UserController 中的一个成员变量,成员变量需要考虑线程安全的问题
多个请求同时到达 UserController 时,每个请求都能修改 count 变量,就会出现线程安全问题
而 getById 方法中的形参是一个局部变量,局部变量一般不存在线程安全的问题
那 UserController 中的 userService 也是成员变量,会不会存在线程安全的问题呢
先说结论,userService 成员变量不存在线程安全的问题,为什么呢,因为 userService 是一个无状态的成员变量
因为 userService 成员变量中没有可变的状态,所以在某种程度上来说,UserController 类是线程安全的
3. 怎么理解无状态
怎么理解无状态呢,简单来说,就是判断某个成员变量能不能被修改,以上述的 UserController 类来说
- count 成员变量可以被修改,所以 count 成员变量是有状态的
- userService 不能被修改,所以 userService 成员变量是无状态的
在开发过程中,我们也要避免使用有状态的成员变量
有同学可能会有疑问,userService 变量怎么就不能被修改了,我将 userService 置为 null,不也算是修改了吗
确实如此,但这已经不属于线程安全的范畴
我们可以改造一下 UserService 类的注入方式,从使用 @Autowired 注入改成使用构造器注入,并用 final 关键字修饰 userService 成员变量,在编译期禁止修改 userService 成员变量
4. 总结
因为一般在 Spring 的单例 Bean 中注入的都是无状态的对象,所以没有线程安全问题,但如果在 Bean 中定义了可修改的成员变量,需要考虑线程安全问题,可以通过加锁等方式来解决