Spring创建的Bean对象存在线程安全问题吗?

 作者简介:大家好,我是码炫码哥,前中兴通讯、美团架构师,现任某互联网公司CTO,兼职码炫课堂主讲源码系列专题


代表作:《jdk源码&多线程&高并发》,《深入tomcat源码解析》,《深入netty源码解析》,《深入dubbo源码解析》,《深入springboot源码解析》,《深入spring源码解析》,《深入redis源码解析》等


联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬。码炫课堂的个人空间-码炫码哥个人主页-面试,源码等

要理解这个问题 ,我们需要先明白以下几个知识点 :

  1. Spring Bean 的作用域
  2. 单例是否是线程安全的?

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 默认为单例,下面我们先看单例是否为线程安全?

单例是否是线程安全的?

单例对象是否为线程安全取决于两个方面:

  1. 是否存在全局变量
  2. 使用全局变量的地方是否保证了线程安全

如果单例对象中的方法全部都是局部变量,那么肯定是线程安全的。因为 :

  1. Java虚拟机栈是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
  2. 局部变量的固有属性之一就是封闭在执行线程中,它们位于执行线程的栈中,其他线程无法访问这个栈。

故而,局部变量不被其他线程共享,所以是线程安全的。

如果存在全局变量就一定是线程不安全的?不一定,分为以下几种情况:

  1. 不可变:如果该全局变量是不可变的(使用了 final 修饰),那么它是线程安全的。
  2. 封装:尽管一个对象可变,但是如果我们做了良好的封装(例如,通过使用 getter/setter 方法,并在这些方法中提供同步),则也能保证线程安全。
  3. 同步:对全局变量的访问全部都做了必要的同步操作,例如使用 synchronized 、Lock 等等其他同步操作,也可以保证线程安全。
  4. 原子操作:使用 java.util.concurrent.atomic 中的类来管理全局变量,也可以提供线程安全。
  5. 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 的线程安全我们一般都需要遵循如下几个原则:

  1. Bean 无状态化:无状态Bean 通常是线程安全的。
  2. 局部变量和局部数据存储:局部变量存储在线程栈中,为每个线程独立访问,自然线程安全。
  3. 同步访问共享资源:如果一定要使用共享资源或有状态的 Bean,请确保这些资源的访问都是同步的。
  4. 使用不可变对象:不可变对象一旦创建就不会改变,所以它们天然是线程安全的。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值