要理解这个问题 ,我们需要先明白以下几个知识点 :
- 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,请确保这些资源的访问都是同步的。
- 使用不可变对象:不可变对象一旦创建就不会改变,所以它们天然是线程安全的。
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] ,回复【面试题】 即可免费领取。