问题

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事务在多线程下不生效,原因是:

  1. Spring事务基于ThreadLocal实现,数据库连接和事务同步信息存储在ThreadLocal中
  2. ThreadLocal是线程隔离的,子线程无法获取父线程的事务上下文
  3. 新线程没有事务管理,数据会自动提交(autocommit=true)

正确做法:

  • 为每个子线程单独开启事务(调用带 @Transactional 的方法)
  • 使用 TransactionTemplate 在子线程中手动管理事务
  • 避免在事务方法中使用多线程,改为同步批量处理
  • 分布式场景使用分布式事务框架(如Seata)

面试加分点: 能说出ThreadLocal的原理,以及为什么InheritableThreadLocal也不能解决Spring事务的多线程问题。