未修改ArrayList遍历抛出ArrayList$Itr.checkForComodification异常排查求助
问题根因分析
- 首先明确:
java.util.ConcurrentModificationException本质是ArrayList的fail-fast机制触发:迭代器遍历过程中检测到集合的modCount(修改计数)和迭代器初始化时记录的值不一致就会抛出,不止增删元素会修改modCount,JDK 8+的ArrayList.sort()方法执行时也会更新modCount,这是本次问题的核心触发条件。 - 你当前的代码执行顺序是:获取缓存的
blockDTO实例 -> 调用ServiceUtility.sortBlock对其内部的dataDTO集合排序 -> 遍历dataDTO集合。如果两个线程同时拿到同一个blockDTO实例,线程A正在执行排序修改modCount,线程B正在遍历集合走迭代器逻辑,就会直接抛出异常。 - 你之前观测到的哈希值不同不能证明是不同对象实例:如果
AbstractBlockDTO重写了hashCode()方法,你打印的是重写后的哈希值,而非JVM层面的对象身份标识。你可以打印System.identityHashCode(blockDTO.getDataDTO())确认是否是同一个集合实例,只有身份哈希值相同才代表是同一个对象。 - 缓存返回共享实例的可能性:你使用的Spring Caching + Memcached组合,如果配置了反序列化优化、或者客户端开启了对象复用逻辑,高并发场景下可能出现同一个缓存键的反序列化对象被多个线程共享的情况,压测时并发量高更容易触发。
可行解决方案
- 防御性拷贝(最稳妥,推荐优先使用)
拿到缓存返回的dataDTO集合后,先拷贝生成新的ArrayList再做后续操作,完全隔离缓存实例和业务逻辑的修改,代码示例:
- 防御性拷贝(最稳妥,推荐优先使用)
@Override @Transactional public AbstractBlockDTO getOtherInfo(long userId, long sectionId) { AbstractBlockDTO blockDTO = questionService.getQuestionBlockData(userId, sectionId); // 对集合做防御性拷贝 List<AbstractDataDTO> copiedList = new ArrayList<>(blockDTO.getDataDTO()); blockDTO.setDataDTO(copiedList); ServiceUtility.sortBlock(blockDTO); User user = getUser(userId); for (AbstractDataDTO dataDTO : blockDTO.getDataDTO()) { // 原有业务逻辑不变 String questionBlockName = dataDTO.getId(); if (questionBlockName.equalsIgnoreCase(Constants.ETHNICITY)) { populateEthnicityInfo(dataDTO, user, instanceId); } else if (questionBlockName.equalsIgnoreCase(Constants.RACE)) { populateRaceInfo(dataDTO, user, instanceId); } else if (questionBlockName.equalsIgnoreCase(Constants.ETHNICITY_2)) { populateEthnicity2(dataDTO, user, instanceId); } } return blockDTO; }
- 调整缓存配置
确认Memcached客户端的序列化配置,关闭对象复用逻辑,强制每次读取缓存都完整反序列化生成独立的对象实例,避免多个线程共享同一个集合对象。
- 调整缓存配置
- 缓存层预先排序(可选优化)
如果排序逻辑是固定的,可以在存入缓存之前就把集合排好序,业务读取缓存后不需要再执行排序操作,从根源上避免修改缓存返回的集合对象。
- 缓存层预先排序(可选优化)
内容的提问来源于stack exchange,提问作者Eager
相关产品推荐
相关产品推荐

