You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot中如何避免@Async方法参数列表被清空?

问题:Spring Boot @Async异步批量插入时列表被清空导致数据丢失

在CSV数据导入数据库的场景中,每积累10000条记录就调用标注了@Async的addInputDataStagingV12方法,调用后主线程立即清空stagingData列表,结果异步方法中的列表也被清空,无法插入数据;移除@Async注解后功能正常。

相关代码

主方法所在类

@Async("threadPoolTaskExecutor")
@Override
public CommonResponse generateDedupInputFile(String inputRequest){

   if (stagingData.size() == 10000) {

            futures.add(databaseUpdateChanges.addInputDataStagingV12("tbl_staging", fieldQuery,
                    config.getInputFields(), batchId, stagingData));

            logger.info("staging size: {}", stagingData.size());
            
            stagingData.clear();

        }

}

DB操作类

@Async("threadPoolTaskExecutor")
@Transactional
public Future<String> addInputDataStagingV12(String tableName, String fieldQuery, List<InputField> inputFields,
        BigInteger batchId, List<StagingDataModel> stagingDataModels) {

    logger.info("Size : {}", stagingDataModels.size()); // 此处输出大小为0
    /** 数据插入查询逻辑 **/   

}

问题原因

Java中List是引用类型,主线程调用异步方法时传递的是stagingData的引用,而非副本。异步方法在后台线程执行时,主线程已经调用了stagingData.clear(),导致异步方法持有的同一个引用指向的列表内容被清空。

同步调用时,addInputDataStagingV12方法会执行完成后才会执行clear(),因此不会出现问题。

解决方案

方案1:传递列表副本(推荐)

在调用异步方法时,创建stagingData的副本传递进去,这样异步方法操作的是独立的列表,主线程清空原列表不会影响异步任务:

if (stagingData.size() == 10000) {
    // 创建副本传递给异步方法
    futures.add(databaseUpdateChanges.addInputDataStagingV12("tbl_staging", fieldQuery,
            config.getInputFields(), batchId, new ArrayList<>(stagingData)));

    logger.info("staging size: {}", stagingData.size());
    
    stagingData.clear();
}

方案2:新建列表替代清空操作

每次批量提交后,直接创建新的列表替换原stagingData,原列表的引用被异步方法持有,不会被清空:

if (stagingData.size() == 10000) {
    futures.add(databaseUpdateChanges.addInputDataStagingV12("tbl_staging", fieldQuery,
            config.getInputFields(), batchId, stagingData));

    logger.info("staging size: {}", stagingData.size());
    
    // 新建列表,原列表留给异步任务处理
    stagingData = new ArrayList<>();
}

方案3:等待异步任务完成后再清空(不推荐)

通过Future.get()阻塞主线程,等待异步任务执行完成后再清空列表,但这样会失去异步调用的意义,退化为同步执行:

if (stagingData.size() == 10000) {
    Future<String> future = databaseUpdateChanges.addInputDataStagingV12("tbl_staging", fieldQuery,
            config.getInputFields(), batchId, stagingData);
    futures.add(future);
    // 等待异步任务完成
    try {
        future.get();
    } catch (InterruptedException | ExecutionException e) {
        // 异常处理逻辑
        logger.error("异步任务执行失败", e);
    }
    logger.info("staging size: {}", stagingData.size());
    
    stagingData.clear();
}

内容的提问来源于stack exchange,提问作者Yugesh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 08:16:04