作者简介:大家好,我是码炫码哥,前中兴通讯、美团架构师,现任某互联网公司CTO,兼职码炫课堂主讲源码系列专题
代表作:《jdk源码&多线程&高并发》,《深入tomcat源码解析》,《深入netty源码解析》,《深入dubbo源码解析》,《深入springboot源码解析》,《深入spring源码解析》,《深入redis源码解析》等
联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬。码炫课堂的个人空间-码炫码哥个人主页-面试,源码等
要理解这个问题 ,我们需要先明白以下几个知识点 :
- Spring Bean 的作用域
- 单例是否是线程安全的?
Spring Bean 的作用域
Spring Bean 的作用域有 5 中:
作用域 | 字符 | 描述 |
---|---|---|
单例 | singleton | 默认作用域,在Spring IoC容器仅存在一个Bean实例,Bean以单例形式存在 |
原型 | prototype | 每次从容器中获取Bean时,都会返回一个新的实例,即每次调用类似getBean() 时,都相当于执行new XxxBean() |
请求 | request | 每次 HTTP 请求都会创建一个新的 Bean |
会话 | session | 同一个 HTTP Session共享一个Bean,不同Session使用不同的Bean |
全局 | application | 在一个 Http Servlet Context 中,定义一个 Bean 实例 |
关于作用域详情请看文章:Spring 中的 Bean 有几种作用域?有哪些使用场景?
Spring Bean 默认为单例,下面我们先看单例是否为线程安全?
单例是否是线程安全的?
单例对象是否为线程安全取决于两个方面:
- 是否存在全局变量
- 使用全局变量的地方是否保证了线程安全
如果单例对象中的方法全部都是局部变量,那么肯定是线程安全的。因为 :
- Java虚拟机栈是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
- 局部变量的固有属性之一就是封闭在执行线程中,它们位于执行线程的栈中,其他线程无法访问这个栈。
故而,局部变量不被其他线程共享,所以是线程安全的。
如果存在全局变量就一定是线程不安全的?不一定,分为以下几种情况:
- 不可变:如果该全局变量是不可变的(使用了
final
修饰),那么它是线程安全的。 - 封装:尽管一个对象可变,但是如果我们做了良好的封装(例如,通过使用 getter/setter 方法,并在这些方法中提供同步),则也能保证线程安全。
- 同步:对全局变量的访问全部都做了必要的同步操作,例如使用 synchronized 、Lock 等等其他同步操作,也可以保证线程安全。
- 原子操作:使用
java.util.concurrent.atomic
中的类来管理全局变量,也可以提供线程安全。 ThreadLocal
:使用ThreadLocal
类使每个线程都有自己的变量副本,从而避免共享访问,这在某些情况下也可以保证线程安全。
所以,在单例模式下,如果该对象没有全局变量则可以保证线程安全,就算存在全局变量,只要我们做了合适的设计,也是可以保证线程安全的。
知道这个前提,我们就可以来回答这个问题了。
Spring创建的Bean对象存在线程安全问题吗
Spring 创建的 Bean 对象默认为单例作用域,Spring 不保证其线程安全,由开发者自行来设计。这里码哥敲几个实例来演示下。
- 默认情况下
@RestController
public class TestController {
private int count = 0;
@GetMapping("/test")
public String test() {
System.out.println("Count = " + count++);
return "Count =" + count;
}
}
执行结果:
Count = 0
Count = 1
Count = 2
这种情况下肯定是线程不全的。那怎么解决呢?网上很多方案是说调整 @Scope(value = "prototype")
,因为每次都是一个新的对象,count = 0
,我们看下:
@Scope(value = "prototype")
@RestController
@Scope(value = "prototype")
public class TestController {
// 省略...
}
执行结果:
Count = 0
Count = 0
Count = 0
这种确实是解决了,但是如果 Count 为 static 呢?
- 全局变量为 static
@RestController
@Scope(value = "prototype")
public class TestController {
private static int count = 0;
// 省略...
}
执行结果:
Count = 0
Count = 1
Count = 2
所以加了 @Scope(value = "prototype")
也不能保证所有情况下都是线程安全的。故而,线程安全还是取决于我们怎么定义变量以及 Bean 的配置。
所以为了保证 Spring Bean 的线程安全我们一般都需要遵循如下几个原则:
- Bean 无状态化:无状态Bean 通常是线程安全的。
- 局部变量和局部数据存储:局部变量存储在线程栈中,为每个线程独立访问,自然线程安全。
- 同步访问共享资源:如果一定要使用共享资源或有状态的 Bean,请确保这些资源的访问都是同步的。
- 使用不可变对象:不可变对象一旦创建就不会改变,所以它们天然是线程安全的。