ExecutorService处理ArrayList数据异常:部分未处理且重复执行最后元素
问题排查与修复
核心问题1:复用单例Runnable实例导致线程安全问题
你的ShopifyBulkSyncCoupons被标记为@Component,这意味着Spring会创建唯一的单例实例。在循环中你反复复用这个实例,不断调用setCouponId(id),而线程池中的线程可能还未开始执行前一个任务,此时id就被下一次循环覆盖了。这会直接导致:
- 多个线程执行同一个id的任务
- 部分id被直接跳过(被后续id覆盖)
- 最终出现"仅第二、第三和最后一个元素被处理,最后一个反复执行"的现象
修复方案:每次循环创建新的任务实例
不要复用Spring注入的单例Bean,改为每次循环创建新的ShopifyBulkSyncCoupons实例,或者将该类改为原型模式(@Scope("prototype"))。
方案一:手动创建新实例
修改后的syncShopifyPriceRulesListToMongoV3方法:
public ResponseEntity<?> syncShopifyPriceRulesListToMongoV3(List<Long> ids) { logger.info("Start :: syncShopifyPriceRulesListToMongoV3 for ids size = "+ids.size()); ExecutorService service = Executors.newFixedThreadPool(3); try { if (!ids.isEmpty()) { for (Long id : ids) { logger.info("id inside for"+id); // 每次创建新的任务实例,避免单例共享状态 ShopifyBulkSyncCoupons task = new ShopifyBulkSyncCoupons(); // 手动注入依赖(如果ShopifyBulkSyncCoupons依赖其他Spring Bean) task.setShopifyClient(shopifyClient); task.setProductAttrRepository(productAttrRepository); task.setPriceRuleDetailsRepo(priceRuleDetailsRepo); task.setCouponId(id); service.execute(task); } } } catch (Exception e) { throw new RuntimeException(e); } logger.info("End :: syncShopifyPriceRulesListToMongoV3"); // 等待所有任务完成再返回,避免主线程提前结束导致任务异常 try { service.shutdown(); if (!service.awaitTermination(60, TimeUnit.SECONDS)) { service.shutdownNow(); } } catch (InterruptedException e) { service.shutdownNow(); Thread.currentThread().interrupt(); } return new ResponseEntity<>(HttpStatus.OK); }
方案二:将任务类改为原型作用域
修改ShopifyBulkSyncCoupons的作用域,让Spring每次返回新实例:
@Component @Scope("prototype") // 每次获取都是新实例 public class ShopifyBulkSyncCoupons implements Runnable { // ... 原有代码 }
此时在循环中通过Spring容器获取新实例:
// 注入ApplicationContext @Autowired private ApplicationContext ctx; // 循环中获取原型实例 ShopifyBulkSyncCoupons task = ctx.getBean(ShopifyBulkSyncCoupons.class); task.setCouponId(id); service.execute(task);
核心问题2:Run方法未处理异常,导致线程静默终止
你的run()方法没有捕获全局异常,如果shopifyClient.getPriceRuleById(CouponId)或数据库操作抛出异常,线程会直接终止且无日志输出,这会导致部分任务看起来未执行,甚至线程池线程数量减少,后续任务分配异常。
修复方案:添加全局异常捕获
@Override public void run() { logger.info("Inside run for id "+CouponId); logger.info("ShopifyClientBean "+shopifyClient); try { PriceRuleService priceRuleService = new PriceRuleServiceImpl(); ShopifyPriceRuleResponse priceRuleResponse = shopifyClient.getPriceRuleById(CouponId); if(null!=priceRuleResponse && null!=priceRuleResponse.getPriceRule()) { PriceRuleDetails priceRuleDetails = new PriceRuleDetails(); List<Long> finalEntitledVariants = new ArrayList<>(); priceRuleDetails = priceRuleService.mapShopifyPriceRuleToModel(priceRuleResponse.getPriceRule()); if (!CollectionUtils.isEmpty(priceRuleDetails.getEntitledCollectionIds())) { List<Long> collectionIds = priceRuleDetails.getEntitledCollectionIds(); List<ProductAttributes> prodAttr = productAttrRepository.findByCollectionsIn(collectionIds); for (ProductAttributes prod : prodAttr) { finalEntitledVariants.add(prod.getVariantId()); } } if (!CollectionUtils.isEmpty(priceRuleDetails.getEntitledProductIds())) { List<ProductAttributes> productAttributesList = productAttrRepository.findByProductIdIn(priceRuleDetails.getEntitledProductIds()); for (ProductAttributes prod : productAttributesList) { finalEntitledVariants.add(prod.getVariantId()); } } if (!CollectionUtils.isEmpty(priceRuleDetails.getEntitledVariantIds())) { finalEntitledVariants.addAll(priceRuleDetails.getEntitledVariantIds()); } priceRuleDetails.setEntitledVariantIds(finalEntitledVariants); priceRuleDetailsRepo.save(priceRuleDetails); } } catch (Exception e) { logger.error("Failed to process coupon id: "+CouponId, e); // 可根据业务需求添加重试、告警等逻辑 } }
额外优化:避免在run方法中创建Service实例
new PriceRuleServiceImpl()会每次创建新的Service实例,建议将PriceRuleService注入为Spring Bean,通过构造注入到ShopifyBulkSyncCoupons中,提升性能和可维护性:
@Component @Scope("prototype") public class ShopifyBulkSyncCoupons implements Runnable { private final PriceRuleService priceRuleService; private final ProductAttrRepository productAttrRepository; private final PriceRuleDetailsRepo priceRuleDetailsRepo; private final ShopifyClient shopifyClient; private Long couponId; // 构造注入所有依赖 @Autowired public ShopifyBulkSyncCoupons(PriceRuleService priceRuleService, ProductAttrRepository productAttrRepository, PriceRuleDetailsRepo priceRuleDetailsRepo, ShopifyClient shopifyClient) { this.priceRuleService = priceRuleService; this.productAttrRepository = productAttrRepository; this.priceRuleDetailsRepo = priceRuleDetailsRepo; this.shopifyClient = shopifyClient; } public void setCouponId(Long couponId) { this.couponId = couponId; } @Override public void run() { logger.info("Inside run for id "+couponId); logger.info("ShopifyClientBean "+shopifyClient); try { ShopifyPriceRuleResponse priceRuleResponse = shopifyClient.getPriceRuleById(couponId); if(null!=priceRuleResponse && null!=priceRuleResponse.getPriceRule()) { PriceRuleDetails priceRuleDetails = priceRuleService.mapShopifyPriceRuleToModel(priceRuleResponse.getPriceRule()); List<Long> finalEntitledVariants = new ArrayList<>(); if (!CollectionUtils.isEmpty(priceRuleDetails.getEntitledCollectionIds())) { List<Long> collectionIds = priceRuleDetails.getEntitledCollectionIds(); List<ProductAttributes> prodAttr = productAttrRepository.findByCollectionsIn(collectionIds); prodAttr.forEach(prod -> finalEntitledVariants.add(prod.getVariantId())); } if (!CollectionUtils.isEmpty(priceRuleDetails.getEntitledProductIds())) { List<ProductAttributes> productAttributesList = productAttrRepository.findByProductIdIn(priceRuleDetails.getEntitledProductIds()); productAttributesList.forEach(prod -> finalEntitledVariants.add(prod.getVariantId())); } if (!CollectionUtils.isEmpty(priceRuleDetails.getEntitledVariantIds())) { finalEntitledVariants.addAll(priceRuleDetails.getEntitledVariantIds()); } priceRuleDetails.setEntitledVariantIds(finalEntitledVariants); priceRuleDetailsRepo.save(priceRuleDetails); } } catch (Exception e) { logger.error("Failed to process coupon id: "+couponId, e); } } }
内容的提问来源于stack exchange,提问作者Shreya Aggarwal
相关产品推荐
相关产品推荐

