在Runnable中使用Collections.sort排序为何偶尔出现重复项?
解决联系人列表排序后出现重复/缺失项的问题
问题根源
- 并发修改同一列表实例:代码直接操作
mContactListLiveData.getValue()返回的原列表,当updateSortedContactList被多次调用时,前一次的排序任务若未被成功终止(cancel(true)无法中断正在执行的排序操作),会和新任务同时修改同一个列表,引发数据错乱。 - 原地排序的副作用:
Collections.sort是原地排序,若该列表还被其他组件持有引用,并发访问会导致数据异常。 - 任务取消逻辑失效:
cancel(true)仅能中断处于等待状态的线程,对已在执行的排序任务无效,新旧任务的排序操作会冲突。
修复方案
1. 创建列表副本,隔离修改操作
每次排序前复制原列表,确保每个排序任务操作独立的列表实例,避免互相干扰:
List<Contact> contactList = new ArrayList<>(mContactListLiveData.getValue());
2. 改用单线程执行器,避免并发冲突
初始化线程池时使用SingleThreadExecutor,保证同一时间只有一个排序任务执行,从根源消除并发问题:
private ExecutorService mExecutorService = Executors.newSingleThreadExecutor();
此时无需手动取消任务,新任务会自动排队等待前一个任务完成。
3. 优化任务取消逻辑(若保留多线程池)
如果必须使用多线程池,将取消参数改为false,避免中断正在执行的线程,让任务自然完成,同时通过列表副本隔离修改:
mRunnableFuture.cancel(false);
修复后的完整代码
private void updateSortedContactList() { List<Contact> sourceList = mContactListLiveData.getValue(); if (sourceList == null || sourceList.isEmpty()) { setValue(null); return; } if (mRunnableFuture != null) { mRunnableFuture.cancel(false); } Runnable runnable = () -> { // 创建独立副本,避免修改原列表 List<Contact> contactList = new ArrayList<>(sourceList); Collections.sort(contactList, comparator); postValue(new Pair<>(sortMethod, contactList)); }; mRunnableFuture = mExecutorService.submit(runnable); }
额外注意事项
- 禁止直接修改LiveData返回的列表实例,因为LiveData的数据源可能被多个观察者共享,修改原实例会导致所有观察者拿到错乱数据。
- 若联系人数据量较小,可直接在主线程执行排序,完全避免多线程并发问题。
内容的提问来源于stack exchange,提问作者Miled
相关产品推荐
相关产品推荐

