在 Spring 中,线程并发问题的核心是共享资源的竞争(如多线程读写同一变量、并发操作数据库等),注解本身不能直接解决并发问题,但 Spring 提供了配套注解结合并发编程规范,可以优雅地实现线程安全控制。
下面我会从最常用的场景出发,讲解 Spring 中如何通过注解解决并发问题,包括方法级别的同步控制 、分布式锁(解决多实例并发)等核心方案。
一、基础场景:单实例下的方法同步(@Synchronized 或 @Lock)
Spring 本身没有内置的 @Synchronized 注解,但可以通过 AOP 自定义注解 实现方法级别的同步锁,替代原生 synchronized 关键字,让代码更优雅。
1. 自定义同步注解 + AOP 实现
步骤 1:定义同步注解
import java.lang.annotation.*;
/**
* 自定义同步注解,用于标记需要同步的方法
*/
@Target(ElementType.METHOD) // 仅作用于方法
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MethodSynchronized {
// 可指定锁的名称(可选)
String lockKey() default "";
}
步骤 2:实现 AOP 切面(核心)
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 同步注解的切面实现
*/
@Aspect
@Component // 交给 Spring 管理
public class SynchronizedAspect {
// 维护锁的缓存,避免重复创建锁
private final ConcurrentHashMap<String, Lock> lockMap = new ConcurrentHashMap<>();
/**
* 环绕通知:拦截所有标注了 @MethodSynchronized 的方法
*/
@Around("@annotation(methodSynchronized)")
public Object around(ProceedingJoinPoint joinPoint, MethodSynchronized methodSynchronized) throws Throwable {
// 1. 确定锁的 key(默认用方法全限定名,也可自定义)
String lockKey = methodSynchronized.lockKey().isEmpty()
? joinPoint.getSignature().toLongString()
: methodSynchronized.lockKey();
// 2. 获取或创建锁(ConcurrentHashMap 保证线程安全)
Lock lock = lockMap.computeIfAbsent(lockKey, k -> new ReentrantLock());
try {
lock.lock(); // 加锁
return joinPoint.proceed(); // 执行原方法
} finally {
lock.unlock(); // 释放锁(finally 确保锁一定会释放)
}
}
}
步骤 3:使用注解解决并发问题
import org.springframework.stereotype.Service;
@Service
public class OrderService {
// 共享资源:模拟库存
private int stock = 100;
/**
* 扣减库存(并发场景)
* 使用 @MethodSynchronized 保证方法执行的原子性
*/
@MethodSynchronized
public void deductStock(int num) {
if (stock >= num) {
// 模拟耗时操作(如数据库查询)
try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); }
stock -= num;
System.out.println("扣减成功,剩余库存:" + stock);
} else {
System.out.println("库存不足");
}
}
// 获取库存(测试用)
public int getStock() {
return stock;
}
}
步骤 4:测试并发效果
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class TestConcurrent {
public static void main(String[] args) {
// 初始化 Spring 容器
AnnotationConfigApplicationContext context =
new AnnotationConfigApplicationContext("com.example");
OrderService orderService = context.getBean(OrderService.class);
// 模拟 10 个线程并发扣减库存(每个扣减 10)
for (int i = 0; i < 10; i++) {
new Thread(() -> orderService.deductStock(10)).start();
}
}
}
输出结果(无并发问题,库存最终为 0):
扣减成功,剩余库存:90
扣减成功,剩余库存:80
...
扣减成功,剩余库存:0
2. 进阶:按参数加锁(解决细粒度并发)
如果希望仅对相同参数的请求加锁(比如同一订单号的并发操作),可以修改 AOP 切面,将参数纳入锁的 key:
// 修改 SynchronizedAspect 的 around 方法
String lockKey = methodSynchronized.lockKey().isEmpty()
? joinPoint.getSignature().toLongString() + Arrays.toString(joinPoint.getArgs())
: methodSynchronized.lockKey() + Arrays.toString(joinPoint.getArgs());
二、分布式场景:多实例并发(@RedissonLock)
如果你的应用部署在多台服务器(多实例),本地锁(如 ReentrantLock、synchronized)会失效,此时需要用分布式锁,Spring 中可结合 Redis(Redisson)实现注解式分布式锁。
1. 依赖引入(Maven)
xml
<!-- Redisson 分布式锁核心依赖 -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.23.3</version>
</dependency>
2. 自定义分布式锁注解
import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RedissonLock {
// 锁的 key(支持 SpEL 表达式,如 "#orderId")
String key();
// 锁的过期时间(防止死锁)
long expireTime() default 30;
// 时间单位
TimeUnit timeUnit() default TimeUnit.SECONDS;
}
3. AOP 实现分布式锁切面
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@Aspect
@Component
public class RedissonLockAspect {
@Autowired
private RedissonClient redissonClient;
// SpEL 表达式解析器
private final ExpressionParser parser = new SpelExpressionParser();
// 参数名解析器(获取方法参数名)
private final DefaultParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
@Around("@annotation(redissonLock)")
public Object around(ProceedingJoinPoint joinPoint, RedissonLock redissonLock) throws Throwable {
// 1. 解析 SpEL 表达式,获取锁的 key
String key = parseSpEL(joinPoint, redissonLock.key());
// 2. 获取分布式锁
RLock lock = redissonClient.getLock(key);
try {
// 3. 加锁(支持过期时间,防止死锁)
boolean locked = lock.tryLock(0, redissonLock.expireTime(), redissonLock.timeUnit());
if (!locked) {
throw new RuntimeException("获取分布式锁失败,请稍后重试");
}
// 4. 执行原方法
return joinPoint.proceed();
} finally {
// 5. 释放锁(仅当前线程持有锁时释放)
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
// 解析 SpEL 表达式
private String parseSpEL(ProceedingJoinPoint joinPoint, String spEL) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
// 获取方法参数名
String[] parameterNames = parameterNameDiscoverer.getParameterNames(method);
// 构建 SpEL 上下文
StandardEvaluationContext context = new StandardEvaluationContext();
Object[] args = joinPoint.getArgs();
for (int i = 0; i < parameterNames.length; i++) {
context.setVariable(parameterNames[i], args[i]);
}
// 解析表达式
return parser.parseExpression(spEL).getValue(context, String.class);
}
}
4. 使用分布式锁注解
@Service
public class OrderService {
/**
* 提交订单(分布式并发场景)
* @param orderId 订单号(按订单号加锁,不同订单不互斥)
*/
@RedissonLock(key = "'order:' + #orderId") // SpEL 表达式,最终 key 为 "order:123456"
public void submitOrder(String orderId) {
// 并发操作:如扣减库存、生成订单
System.out.println("订单 " + orderId + " 提交成功");
}
}
三、其他补充方案
-
Spring 事务注解(@Transactional) :虽然
@Transactional主要用于事务管理,但它的隔离级别 (如Isolation.REPEATABLE_READ)可以解决数据库层面的并发问题(如脏读、不可重复读)。例如:@Transactional(isolation = Isolation.REPEATABLE_READ) public void updateUserBalance(Long userId, BigDecimal amount) { // 数据库并发更新操作 } -
Spring 缓存注解(@Cacheable) :对于读多写少的场景,
@Cacheable可以将查询结果缓存到 Redis 等介质,减少数据库的并发访问,间接降低并发压力。
总结
- 单实例并发 :通过自定义 @MethodSynchronized 注解 + AOP 实现方法级同步锁,替代原生
synchronized,支持细粒度(按参数)加锁。 - 分布式并发 :结合 Redisson 实现 @RedissonLock 注解,通过 Redis 分布式锁解决多实例的并发问题,核心是 "锁的 key 唯一性 + 自动过期防死锁"。
- 数据库层并发 :使用
@Transactional配置合适的隔离级别,解决数据库层面的并发问题;读多写少场景可结合@Cacheable减少并发压力。
核心原则:注解是 "语法糖",真正解决并发的是锁机制 (本地锁 / 分布式锁)和原子性保证,注解只是让锁的使用更优雅、更易维护。