文章中如果有图看不到,可以点这里去 csdn 看看。从那边导过来的,文章太多,没法一篇篇修改好。

Java 框架 Spring Bean 加载、创建、管理层面线程安全吗

核心结论:

Spring IoC 容器本身在 Bean 的加载、创建、和管理层面是线程安全的。它的线程安全是通过精心的设计和底层同步机制保证的。然而,线程安全的终极责任落在了我们开发者编写的 Bean 本身的行为上

让我们从容器和业务两个层面来分解这个问题。


一、容器层面:Spring 如何保证自身流程的线程安全

Spring 的线程安全核心在于其容器的数据结构和对这些结构的访问控制。我们重点关注负责单例 Bean 管理的 DefaultSingletonBeanRegistry

1. 三级缓存与同步控制

Bean 的创建中心——三级缓存,是解决循环依赖的关键,也是保证并发创建不会出问题的核心。

public class DefaultSingletonBeanRegistry ... {
    // 一级缓存:存放完全初始化好的单例 Bean (成品)
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
    // 二级缓存:存放早期暴露的 Bean 引用 (半成品),用于解决循环依赖
    private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
    // 三级缓存:存放 ObjectFactory,用于生成早期引用,是解决 AOP 代理与循环依赖的关键
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

    // ★ 关键:所有对上述缓存进行“写操作”的方法都使用 synchronized 同步!
    protected void addSingleton(String beanName, Object singletonObject) {
        synchronized (this.singletonObjects) { // 以一级缓存为锁进行同步
            this.singletonObjects.put(beanName, singletonObject);
            this.singletonFactories.remove(beanName);
            this.earlySingletonObjects.remove(beanName);
        }
    }
    protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
        synchronized (this.singletonObjects) { // 同样以一级缓存为锁
            if (!this.singletonObjects.containsKey(beanName)) {
                this.singletonFactories.put(beanName, singletonFactory);
                this.earlySingletonObjects.remove(beanName);
            }
        }
    }
    protected Object getSingleton(String beanName, boolean allowEarlyReference) {
        // 首先无锁检查一级缓存(快速路径)
        Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
            // 如果未初始化完成且在创建中,则进入同步块
            synchronized (this.singletonObjects) { // 同样以一级缓存为锁
                // 检查二级缓存
                singletonObject = this.earlySingletonObjects.get(beanName);
                if (singletonObject == null && allowEarlyReference) {
                    // 检查三级缓存
                    ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                    if (singletonFactory != null) {
                        // 从三级缓存的 ObjectFactory 中获取早期引用
                        singletonObject = singletonFactory.getObject();
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }
        return singletonObject;
    }
}

流程与线程安全保证:

  1. 并发 getBean():当多个线程同时请求一个尚未创建的单例 Bean 时,都会进入到 doCreateBean 流程。
  2. 同步点:它们都会执行到 addSingletonFactory,将创建 Bean 的 ObjectFactory 加入到三级缓存。这个方法是 synchronized 的,所以多个线程会串行执行,确保三级缓存的数据不会错乱。
  3. “第一个”线程:成功将 ObjectFactory 加入三级缓存的线程继续执行后续的 populate 和 initialize。
  4. “后续”线程:在执行到 getSingleton(beanName, true) 尝试解决依赖时,会因为第一个线程已经将 ObjectFactory 放入三级缓存,从而能够通过 getEarlyBeanReference() 获取到早期引用(可能是原始对象或代理对象)。这个早期引用会被放入二级缓存,供所有后续线程使用。
  5. 最终完成:第一个线程完成初始化后,调用 addSingleton 方法(也是 synchronized 的),将完整 Bean 从二级/三级缓存升级到一级缓存。
  6. 其他线程获取:之后所有线程再调用 getSingleton 时,直接从一级缓存拿到完整的 Bean 返回。

Mermaid 序列图:多线程获取单例 Bean

Thread 1Thread 2DefaultSingletonBeanRegistryAbstractBeanFactorygetBean("mySingleton")getBean("mySingleton")Concurrent callsgetSingleton("mySingleton")null (not created yet)Wins the lockaddSingletonFactory(...) [SYNC]doCreateBean() - Populate, InitializeBlocks then gets lockgetSingleton("mySingleton", true) [SYNC]Finds ObjectFactory in L3 cachefactory.getObject() → gets early referencePuts early reference in L2, removes from L3Returns early reference (e.g., proxy)ReturnsaddSingleton(...) [SYNC]Moves bean from L2 → L1, clears L2/L3Now holds an early referencethat points to the same target as the final bean.Thread 1Thread 2DefaultSingletonBeanRegistryAbstractBeanFactory

通过这种以 singletonObjects 为监视器的同步块和三级缓存机制,Spring 确保了:

  • 一个单例 Bean 只会被创建一次
  • 在 Bean 创建过程中,其他线程不会无限等待,而是可以拿到一个“早期引用”先行使用,从而提升并发效率(避免了在整个 doCreateBean 方法上加锁)。
  • 缓存状态的一致性。

二、业务层面:开发者的责任(线程安全的真正风险点)

容器保证了 Bean 的创建和管理是安全的,但如果 Bean 本身的状态和行为不是线程安全的,那么并发访问时就会出问题。这是绝大多数并发问题的根源。

1. 非线程安全的 Bean (有状态 Bean)

如果你的 Bean 包含了可变的成员变量,并且有方法会修改这个状态,那么它就是非线程安全的。

@Service
public class NotThreadSafeService {

    private int count = 0; // ★ 可变状态

    public void increment() {
        count++; // ★ 非原子操作,多线程下会出问题
    }

    public int getCount() {
        return count;
    }
}
  • 场景:多个线程同时调用 increment() 方法,count 的最终值会小于实际调用次数,因为 count++ 不是原子操作。
  • 结论Spring 不会也不能为你保证业务状态的线程安全。

2. 线程安全的 Bean (无状态或使用同步)

  • 无状态 Bean (最佳实践):这是最推荐的方式。Bean 不包含任何可变的成员变量,它的行为仅由参数和内部逻辑决定。

    @Service
    public class StatelessService { // ★ 没有成员变量,线程安全
    
        public String process(String input) {
            return "Processed: " + input;
        }
    }
    
    • @Controller, @Service, @Repository 注解的类,通常都应该设计成无状态的。
  • 使用同步机制:如果必须有状态,开发者必须自行保证线程安全。

    • 使用 synchronized 关键字
    • 使用 java.util.concurrent 包下的线程安全类 (如 AtomicInteger, ConcurrentHashMap)
    @Service
    public class ThreadSafeService {
        private final AtomicInteger count = new AtomicInteger(0); // ★ 使用原子类
    
        public void increment() {
            count.incrementAndGet(); // ★ 原子操作,线程安全
        }
    }
    

3. Prototype(原型)Scope 的 Bean

  • Spring 每次请求都会创建一个新的 Prototype Bean 实例。
  • 由于每个线程获取到的是不同的实例,所以不存在共享资源竞争的问题,自然是线程安全的。
  • 线程安全问题转移到了 Prototype Bean 内部本身(如果它内部操作了静态变量或共享外部资源)。

三、总结与最佳实践

问题点是否线程安全?原因与解决方案
Spring 单例池 (IoC 容器)通过 synchronized 块和三级缓存机制保证。
无状态单例 BeanBean 没有可变状态,是线程安全的。这是推荐的做法。
有状态单例 BeanBean 包含可变状态,需开发者使用同步机制(如synchronized, Lock, 原子类)来保证线程安全。
Prototype/Request/Session Scope Bean (相对于容器)每个线程/请求持有自己的实例,不存在共享。但Bean内部逻辑仍需自己保证安全。

最终答案:

Bean 的加载和创建过程本身是线程安全的,这是由 Spring Framework 通过同步机制保证的。但是,Bean 被加载到容器中之后,在并发环境下被访问时是否安全,完全取决于 Bean 本身的实现

给你的建议:

  1. 极力推崇无状态设计:这是保证线程安全最简单、最有效的方式。
  2. 明确区分状态:如果必须使用状态,要非常清楚哪些状态是共享的,哪些是线程隔离的。
  3. 优先使用并发工具:而不是自己实现复杂的同步逻辑,优先考虑 java.util.concurrent.* 包下的类。
  4. 谨慎使用 @Scope("prototype"):不要把它当作解决线程安全问题的银弹,因为它会带来更大的创建开销和内存消耗。只有在真正需要每次都创建新实例时才使用它(例如,Bean 本身持有非线程安全的状态且该状态在每次使用后都会变化)。
posted @ 2025-09-09 17:48  NeoLshu  阅读(7)  评论(0)    收藏  举报  来源