Spring 的 Bean 是否安全,核心取决于 Bean 的作用域(Scope) 和 Bean 本身的状态设计,不能一概而论 ------ 单例 Bean 存在线程安全风险,而非单例 Bean 通常是线程安全的。以下是具体分析:
一、核心影响因素:Bean 的作用域
Spring 中默认且最常用的 Bean 作用域是 单例(Singleton) ,其他作用域(如原型、请求域)的线程安全特性完全不同。先明确各作用域的 Bean 实例创建规则:
二、单例 Bean:默认作用域,存在线程安全风险

java
@Component // 默认单例
public class UnsafeSingletonBean {
// 可修改的成员变量(状态)
private int count = 0;
// 多线程并发调用此方法时,会出现线程安全问题
public void increment() {
count++; // 非原子操作,多线程下会导致计数不准
System.out.println("当前计数:" + count);
}
}

java
@Component // 单例
public class SafeSingletonBean {
// 无成员变量(或只有不可修改的常量 final)
private final Logger log = LoggerFactory.getLogger(SafeSingletonBean.class);
// 方法仅依赖入参,无状态修改
public String processData(String input) {
return input.toUpperCase(); // 仅处理入参,不修改 Bean 自身状态
}
}

三、非单例 Bean:通常线程安全

java
@Component
@Scope("prototype") // 原型作用域
public class SafePrototypeBean {
private int count = 0;
public void increment() {
count++;
System.out.println("当前计数:" + count); // 每个实例的 count 独立,无共享
}
}

java
@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestScopeBean {
private String requestId;
public void setRequestId(String requestId) {
this.requestId = requestId;
}
public String getRequestId() {
return requestId;
}
}

四、如何保证单例 Bean 的线程安全?

java
@Component
public class ThreadLocalSingletonBean {
// ThreadLocal:每个线程存储独立的 count 副本
private ThreadLocal<Integer> countThreadLocal = ThreadLocal.withInitial(() -> 0);
public void increment() {
int count = countThreadLocal.get();
countThreadLocal.set(count + 1);
System.out.println("当前线程计数:" + countThreadLocal.get()); // 线程私有,无共享
}
// 注意:ThreadLocal 使用后需清理,避免内存泄漏(如在请求结束时调用)
public void clear() {
countThreadLocal.remove();
}
}

java
@Component
public class SynchronizedSingletonBean {
private int count = 0;
// 加锁同步,确保多线程串行执行
public synchronized void increment() {
count++;
System.out.println("当前计数:" + count);
}
}
五、总结
