问题
Spring的事务在多线程下生效吗?为什么?
答案
一、结论
Spring事务在多线程环境下不生效。
二、原因分析
1. 核心原理
Spring事务的数据库连接(Connection)和事务同步信息存储在 ThreadLocal 中。
// Spring事务核心源码
public abstract class TransactionSynchronizationManager {
// 数据库连接存储在ThreadLocal中
private static final ThreadLocal<Map<Object, Object>> resources =
new NamedThreadLocal<>("Transactional resources");
// 事务同步信息也在ThreadLocal中
private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
new NamedThreadLocal<>("Transaction synchronizations");
}
关键点: ThreadLocal的特性是线程隔离,每个线程都有独立的副本,子线程无法继承父线程的事务上下文。
2. 失效示例
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Transactional
public void batchCreateOrders(List<Order> orders) {
// 主线程中的事务
System.out.println("主线程事务: " + TransactionSynchronizationManager.isActualTransactionActive());
// ❌ 使用多线程处理
orders.parallelStream().forEach(order -> {
// 子线程中无法获取事务
System.out.println("子线程事务: " + TransactionSynchronizationManager.isActualTransactionActive());
orderMapper.insert(order); // 不在事务中
});
}
}
输出结果:
主线程事务: true
子线程事务: false // 子线程中无事务
子线程事务: false
子线程事务: false
3. 验证实验
@Transactional
public void testMultiThread() {
// 主线程插入数据
userMapper.insert(new User("张三"));
// 启动子线程
new Thread(() -> {
// 子线程插入数据
userMapper.insert(new User("李四"));
// 制造异常
int i = 1 / 0;
}).start();
// 主线程制造异常
int j = 1 / 0;
}
结果:
- 主线程的”张三”会回滚(有事务)
- 子线程的”李四”已提交,不会回滚(无事务)
三、正确的解决方案
方案1:为每个线程单独开启事务(推荐)
@Service
public class OrderService {
@Autowired
private OrderService self;
public void batchCreateOrders(List<Order> orders) {
// 使用线程池
ExecutorService executor = Executors.newFixedThreadPool(10);
for (Order order : orders) {
executor.submit(() -> {
// 每个线程独立调用事务方法
self.createOrderInTransaction(order);
});
}
executor.shutdown();
}
@Transactional(rollbackFor = Exception.class)
public void createOrderInTransaction(Order order) {
// 每个线程都有独立的事务
orderMapper.insert(order);
}
}
方案2:使用TransactionTemplate
@Service
public class OrderService {
@Autowired
private TransactionTemplate transactionTemplate;
public void batchCreateOrders(List<Order> orders) {
ExecutorService executor = Executors.newFixedThreadPool(10);
for (Order order : orders) {
executor.submit(() -> {
transactionTemplate.execute(status -> {
try {
orderMapper.insert(order);
return true;
} catch (Exception e) {
status.setRollbackOnly();
return false;
}
});
});
}
executor.shutdown();
}
}
方案3:改为同步批量处理(适合小数据量)
@Transactional(rollbackFor = Exception.class)
public void batchCreateOrders(List<Order> orders) {
// 不使用多线程,在一个事务中批量处理
for (Order order : orders) {
orderMapper.insert(order);
}
}
方案4:使用InheritableThreadLocal(不推荐)
虽然可以使用 InheritableThreadLocal 让子线程继承父线程的变量,但 Spring事务不支持这种方式,因为:
- Spring的事务管理器使用的是
ThreadLocal,不是InheritableThreadLocal - 即使子线程获取了连接,也无法正确管理事务的生命周期(提交/回滚)
四、实战建议
1. 分布式场景
如果是分布式系统,使用分布式事务解决方案:
// Seata分布式事务
@GlobalTransactional
public void batchCreateOrders(List<Order> orders) {
CompletableFuture[] futures = orders.stream()
.map(order -> CompletableFuture.runAsync(() ->
orderService.createOrder(order)
))
.toArray(CompletableFuture[]::new);
CompletableFuture.allOf(futures).join();
}
2. 异步任务
对于不需要强一致性的场景,使用消息队列:
@Transactional
public void createOrder(Order order) {
// 主业务
orderMapper.insert(order);
// 发送消息到MQ,异步处理
rabbitTemplate.convertAndSend("order.exchange", "order.create", order);
}
五、源码验证
// TransactionSynchronizationManager.java
public static Object getResource(Object key) {
// 从ThreadLocal中获取数据库连接
Map<Object, Object> map = resources.get();
return map != null ? map.get(key) : null;
}
// DataSourceTransactionManager.java
protected void doBegin(Object transaction, TransactionDefinition definition) {
// 获取数据库连接并绑定到ThreadLocal
Connection newCon = obtainDataSource().getConnection();
TransactionSynchronizationManager.bindResource(
obtainDataSource(),
new ConnectionHolder(newCon)
);
}
关键: resources.get() 调用的是 ThreadLocal.get(),每个线程只能获取自己的数据。
六、答题总结
简洁回答:
Spring事务在多线程下不生效,原因是:
- Spring事务基于ThreadLocal实现,数据库连接和事务同步信息存储在ThreadLocal中
- ThreadLocal是线程隔离的,子线程无法获取父线程的事务上下文
- 新线程没有事务管理,数据会自动提交(autocommit=true)
正确做法:
- 为每个子线程单独开启事务(调用带
@Transactional的方法) - 使用
TransactionTemplate在子线程中手动管理事务 - 避免在事务方法中使用多线程,改为同步批量处理
- 分布式场景使用分布式事务框架(如Seata)
面试加分点: 能说出ThreadLocal的原理,以及为什么InheritableThreadLocal也不能解决Spring事务的多线程问题。