2024-12-18  阅读(72)
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://www.skjava.com/mianshi/baodian/detail/1128422019

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

  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. 使用不可变对象:不可变对象一旦创建就不会改变,所以它们天然是线程安全的。

Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。

它的内容包括:

  • 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
  • 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
  • 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
  • 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
  • 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
  • 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
  • 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
  • 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw

目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:

想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询

同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。

阅读全文