无状态的单例 Bean 是线程安全的,而有状态的单例 Bean 是非线程安全的,所以总的来说单例 Bean 还是非线程安全的。

什么是有状态和无状态?

有状态的 Bean 是指 Bean 中包含了状态,比如成员变量,而无状态的 Bean 是指 Bean 中不包含状态,比如没有成员变量,或者成员变量都是 final 的。

为什么非线程安全?

Spring 默认的 Bean 是单例模式,意味着容器中只有一个 Bean 实例,所有的线程都会使用并操作这个唯一的 Bean 实例,那么多个线程同时调用修改这个单例 Bean,就会产生线程安全问题。 举个例子:

@Component
public class SingletonBean {
    private int counter = 0;

    public int getCounter() {
        return counter++; 
    }
}

这是一个简单的单例 Bean,有一个计数器,每调用一次加 1,当多个线程同时调用这个 Bean 的 getCounter() 方法时,因为 counter++ 是非原子性操作(先查询再加等),所以最终的结果就会比实际的加等次数少,这就是线程安全问题。

如何保证线程安全?

Spring 中保证单例 Bean 线程安全的手段有以下几个:

  1. 变为原型 Bean:在 Bean 上添加 @Scope("prototype") 注解,将其变为多例 Bean。这样每次注入时返回一个新的实例,避免竞争。
  2. 加锁:在 Bean 中对需要同步的方法或代码块添加同步锁 @Synchronized 或使用 Java 中的线程同步工具 ReentrantLock 等。
  3. 使用线程安全的集合:如 Vector、Hashtable 代替 ArrayList、HashMap 等非线程安全集合。
  4. 变为无状态 Bean:不在 Bean 中保存状态,让 Bean 成为无状态 Bean。无状态的 Bean 没有共享变量,自然也无须考虑线程安全问题。
  5. 使用线程局部变量 ThreadLocal:在方法内部使用线程局部变量 ThreadLocal,因为 ThreadLocal 是线程独享的,所以也不存在线程安全问题。

特殊说明

以上内容来自我的《Java 面试突击训练营》,这门课程是有着十几年工作经验(前 360 开发工程师),10 年面试官经验的我,花费 4 年时间打磨完成的一门视频面试课。学完训练营的课程之后,基本可以应对目前市面上绝大部分公司的面试了,并且课程配备了 9 大就业服务,帮助上千人找到 Java 工作,其中上百人拿到大厂 Offer,学员最高薪资 70W 年薪,面试课目录和 9 大服务如下:

加我微信咨询:vipStone【备注:训练营】