首先,Spring中的单例Bean不是线程安全的
为什么:
对于这个问题,首先我们要知道:
- Spring中的Bean默认是单例的,我们可以通过@Scope注解来进行相应设置
@Scope("singleton") //某个bean在Spring IOC容器中只有一个实例
@Scope("prototype") //某个bean在Spring IOC容器中可以有多个实例
- 我们在什么时候需要考虑线程安全问题,那就是在多线程并发对同一个对象的共享变量进行读写操作的时候。
上面的代码示例就存在线程安全问题。
我们知道,当多个用户同时请求一个服务时,容器会给每个请求分配一个线程,这些线程将会并发执行对应的业务逻辑。
上述代码中有两个成员变量,一个是count,一个是userService,而对象的属性存放的位置是JVM中的堆区,这也就意味着他们将被线程所共享,也就造成了多线程访问共享变量的问题。
那么count和userService都会引发线程安全问题吗?
答案是否定的,只有count会引发线程安全问题,而userService不会,因为我们不会userService进行读写操作,它是由Spring容器来创建和管理的。我们称它是无状态的。而count则不一样,我们在方法getById中对count进行了读写操作。
那么对count的读写为啥会产生线程安全问题呢,其实count++在底层并不是原子操作,这一步在可以分解成如下的字节码指令
getfield #7 //读取count到操作数栈
5 iconst_1 //在操作数栈定义个1
6 iadd //两者相加
7 putfield #7 // 把结果写回内存中去
假设有如下情况 线程A和线程B同时访问这段代码,线程A先读取了count的值,然后执行了+1操作,但还未写回内存中去,此时CPU切换去调度进程B,线程A进入就绪队列等待CPU调度,而B顺利执行完这段代码并将结果写回了内存,此时线程A重新被CPU调度,执行写回操作, 本来count应该进行两次+1操作,而最终只完成了一次+1操作,这就造成了线程安全问题。
如图所示
结论:
当一个spring容器中的单例Bean中都是注入的无状态的对象,那么就不存在线程安全问题,但如果定义了可修改的成员变量,那么就要考虑线程安全问题。
编码时该如何做:
- 我们尽量不要在Bean中去定义可修改的共享变量
- 如果定义了,那么要用锁或者多例来防止出现多线程安全问题导致的数据错误情况。