Spring 作为 Java 企业级开发框架,本身并不直接提供「线程并发问题」的解决方案,但它整合了 Java 并发编程的核心能力,并通过自身的特性(如作用域、事务、AOP 等)帮助开发者规避或解决并发场景下的线程安全问题。
下面我会从「问题根源」「Spring 核心解决方案」「实战示例」三个维度,帮你理清 Spring 处理并发问题的思路。
一、先理解:Spring 中并发问题的核心根源
Spring 容器默认创建的 Bean 是 单例(Singleton) 的(整个应用只有一个实例),这是并发问题的主要诱因:
- 单例 Bean 的成员变量会被所有线程共享
- 多线程同时修改共享变量时,会出现「脏读」「重复更新」「数据不一致」等问题
举个典型的错误示例:
// 单例 Bean,存在并发安全问题
@Component
public class BadCounterService {
// 共享成员变量,多线程访问会出问题
private int count = 0;
public void increment() {
count++; // 非原子操作,多线程下会计数错误
}
public int getCount() {
return count;
}
}
多线程调用 increment() 时,count++ 拆分为「读取 - 加 1 - 写入」三步,线程切换会导致最终计数远小于预期值。
二、Spring 处理并发问题的核心方案
Spring 解决并发问题的思路本质是「遵循 Java 并发编程规范 + 框架特性适配」,核心方案分为以下几类:
1. 避免共享可变状态(最优解)
这是最根本的解决方案:让单例 Bean 无状态化(不定义成员变量,所有数据通过方法参数传递)。
// 无状态 Bean,天然线程安全
@Component
public class GoodCounterService {
// 无成员变量,所有逻辑依赖方法参数/局部变量
public int increment(int count) {
return count + 1; // 局部变量归属于单个线程,无并发问题
}
}
✅ 适用场景:大部分业务服务(Service/Controller),推荐优先使用。
2. 控制 Bean 的作用域
如果业务必须使用有状态的 Bean,可以通过 Spring 作用域限制 Bean 的共享范围:
表格
| 作用域 | 说明 | 线程安全性 |
|---|---|---|
singleton |
默认,全局单例 | 需手动保证线程安全 |
prototype |
每次获取 Bean 都创建新实例 | 线程安全(实例不共享) |
request |
每个 HTTP 请求创建一个实例 | 线程安全(请求内单例) |
session |
每个用户 Session 创建一个实例 | 需考虑同 Session 多线程 |
示例:将 Bean 设为 Request 作用域(适合存储请求级别的临时数据)
@Component
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestScopedBean {
private String requestData; // 仅当前请求的线程可见,无并发问题
}
3. 基于 Java 并发工具的线程安全保障
Spring 不重复造轮子,直接使用 Java java.util.concurrent 包的工具解决并发问题,常见场景:
(1)原子操作:解决「计数 / 累加」并发问题
使用 AtomicInteger/AtomicLong 等原子类,替代普通变量,保证操作的原子性:
@Component
public class AtomicCounterService {
// 原子类,保证 count++ 是原子操作
private final AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet(); // 原子操作,线程安全
}
public int getCount() {
return count.get();
}
}
(2)锁机制:解决「复杂业务逻辑」并发问题
使用 synchronized 或 ReentrantLock 加锁,保证临界区代码的排他执行:
@Component
public class LockCounterService {
private int count = 0;
// 可重入锁(比 synchronized 更灵活)
private final ReentrantLock lock = new ReentrantLock();
public void increment() {
lock.lock(); // 加锁
try {
count++; // 临界区,仅一个线程执行
} finally {
lock.unlock(); // 必须在 finally 中解锁,避免死锁
}
}
public int getCount() {
lock.lock();
try {
return count;
} finally {
lock.unlock();
}
}
}
(3)并发集合:替代非线程安全集合
Spring 中避免使用 ArrayList/HashMap 等非线程安全集合,改用 ConcurrentHashMap/CopyOnWriteArrayList:
@Component
public class ConcurrentCollectionService {
// 线程安全的 Map,替代 HashMap
private final Map<String, String> concurrentMap = new ConcurrentHashMap<>();
public void put(String key, String value) {
concurrentMap.put(key, value); // 线程安全的写入
}
public String get(String key) {
return concurrentMap.get(key);
}
}
4. 事务隔离:解决「数据库层面」的并发问题
大部分业务的并发问题最终落到数据库层(如重复下单、库存超卖),Spring 事务的隔离级别可以解决这类问题:
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
// 事务隔离级别 + 悲观锁/乐观锁,解决库存并发扣减问题
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void createOrder(Long productId) {
// 1. 查询库存(加行锁,悲观锁)
Product product = orderMapper.selectProductForUpdate(productId);
if (product.getStock() <= 0) {
throw new RuntimeException("库存不足");
}
// 2. 扣减库存
orderMapper.decreaseStock(productId);
// 3. 创建订单
orderMapper.createOrder(productId);
}
}
常用数据库并发解决方案:
- 悲观锁:
select ... for update(适合写多读少场景) - 乐观锁:版本号 / 时间戳(
update ... where version = ?,适合读多写少场景)
5. AOP 实现并发控制(进阶)
Spring AOP 可以封装并发控制逻辑(如分布式锁),让业务代码更简洁。例如用注解实现分布式锁:
// 自定义分布式锁注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DistributedLock {
String key(); // 锁的key
}
// AOP 切面实现锁逻辑
@Aspect
@Component
public class DistributedLockAspect {
@Autowired
private RedissonClient redissonClient; // 基于 Redis 的分布式锁
@Around("@annotation(distributedLock)")
public Object around(ProceedingJoinPoint joinPoint, DistributedLock distributedLock) throws Throwable {
String lockKey = distributedLock.key();
RLock lock = redissonClient.getLock(lockKey);
try {
lock.lock(); // 加分布式锁
return joinPoint.proceed(); // 执行业务方法
} finally {
lock.unlock(); // 解锁
}
}
}
// 业务使用
@Service
public class OrderService {
@DistributedLock(key = "order:create:#{productId}")
public void createOrder(Long productId) {
// 下单逻辑(分布式环境下仅一个线程执行)
}
}
三、Spring 并发问题的避坑点
- @Async 异步方法的并发 :
@Async修饰的方法会在新线程执行,需注意异步方法内的共享变量安全; - 事务与并发的冲突:事务的「提交延迟」可能导致多线程读取到未提交的数据,需配合合适的隔离级别;
- 缓存的并发问题:Spring Cache 缓存的是单例 Bean 的结果,需注意缓存键的唯一性,避免缓存穿透 / 击穿。
总结
- Spring 解决并发问题的核心思路是「无状态化优先」,单例 Bean 避免定义可变成员变量;
- 必须使用有状态 Bean 时,优先通过「作用域限制」「Java 原子类 / 锁」「并发集合」保证线程安全;
- 数据库层面的并发问题,需结合 Spring 事务隔离级别 + 悲观锁 / 乐观锁解决,分布式场景可通过 AOP 封装分布式锁。
核心原则:Spring 不替代 Java 并发编程,而是通过框架特性整合并发工具,让开发者更优雅地解决线程安全问题。