为何健身追踪App切换月份后触发ConcurrentModificationException?
嘿,我来帮你拆解下这个困扰你的问题——你遇到的java.util.ConcurrentModificationException,结合错误栈和代码细节来看,核心原因大概率和ArrayList的SubList视图与原列表的强关联有关,具体触发点就是这行代码:
if (completedDates.equals("100")) {completedDatesList.addAll(thisWeekDates);}
问题根源
错误栈里明确指向ArrayList$SubList.size()抛出异常,这说明thisWeekDates很可能是某个原始ArrayList通过subList()方法返回的视图,而非独立的集合实例。ArrayList的SubList本质上只是原列表的一个"动态窗口",它并没有自己存储数据——当原列表发生结构变化(比如切换月份时你修改了原日期列表的元素、新增/删除条目),SubList的内部状态就会和原列表脱节,此时调用SubList的任何方法(比如size(),而addAll()会间接调用它)都会触发ConcurrentModificationException。
另外,如果你在遍历集合的同时修改集合(比如ViewModel的监听回调里同时操作相关日期列表),也会触发这个异常,但结合切换月份的场景,SubList的问题可能性更高。
具体修复方案
将SubList转为独立集合副本
不要直接使用subList()返回的对象,而是把它转换成一个新的ArrayList,彻底切断和原列表的关联:// 先把thisWeekDates转换成独立副本 List<Date> thisWeekDatesCopy = new ArrayList<>(thisWeekDates); if (completedDates.equals("100")) { completedDatesList.addAll(thisWeekDatesCopy); }这样即使原列表后续被修改,副本也不会受影响,自然不会触发异常。
确保集合操作的线程安全
你提到已经用了CopyOnWriteArrayList,但要确认所有涉及日期列表的操作都遵循线程安全原则:- 如果
completedDatesList或原日期列表需要在主线程和后台线程同时操作,务必用CopyOnWriteArrayList替代普通ArrayList,它天生支持并发修改,不会抛出此类异常。 - 避免在for-each遍历集合的过程中直接修改原集合,改用迭代器的
remove()方法,或者先遍历集合副本,再基于副本的结果修改原集合。
- 如果
调整周数据拆分逻辑
在ChangeMonth方法拆分周数据时,检查是否直接返回了原列表的SubList,改成返回新的ArrayList实例:// 原错误写法(返回SubList): // return originalDates.subList(startIndex, endIndex); // 修改为返回独立集合: return new ArrayList<>(originalDates.subList(startIndex, endIndex));
验证步骤
修改完成后,切换月份并尝试新增、修改或删除训练记录,观察是否还会抛出异常。如果问题依然存在,可以排查ViewModel的监听回调中是否存在并发修改集合的情况,必要时在集合操作处添加同步锁(不过优先使用线程安全集合,锁是兜底方案)。
内容的提问来源于stack exchange,提问作者Josh Brett

