【问题标题】:Why is there a ConcurrentModificationException even when list is synchronized?为什么即使列表已同步,也会出现 ConcurrentModificationException?
【发布时间】:2014-09-26 15:35:42
【问题描述】:

我有 Android 多线程应用程序。

两个或多个触发器可能运行相同的代码部分。

我有一个对象列表。

我让它被Collections.synchronizedList同步

private List<WmGroupItemSample> mGroupItemSampleList;

mGroupItemSampleList = new ArrayList<WmGroupItemSample>();
mGroupItemSampleList = Collections.synchronizedList(mGroupItemSampleList);

但是有时我在网上遇到异常:

Collections.sort(mGroupItemSampleList, new GroupItemSampleComparator());

java.util.ConcurrentModificationException
       at java.util.AbstractList$SimpleListIterator.next(AbstractList.java:62)
       at java.util.Collections.sort(Collections.java:1895)
  • 此流程合法吗?
  • 我需要创建副本并在副本上运行排序吗?
  • 为什么Collections.synchronizedList 不能阻止这个异常?

[编辑]

GroupItemSampleComparator

public class GroupItemSampleComparator implements java.util.Comparator<WmGroupItemSample> {

    public GroupItemSampleComparator() {
        super();        
    }

    public int compare(WmGroupItemSample s1, WmGroupItemSample s2) {
       return ( (s2.getStartDate() - s1.getStartDate()) > 0 ) ? (-1) : (1);
    }
}

谢谢,

【问题讨论】:

  • 这个异常可以在没有任何[附加]线程的情况下重现 - 因此,同步没有任何意义。
  • 您使用的是哪个 Java 版本?我查看了一些 Collections.java,但第 1895 行并不靠近 sort。
  • 伙计们,你们为什么要关注比较器?
  • @pavel:我记得读过原始问题,好像这是在没有多个线程的情况下发生的。这就是为什么每个人都要求比较器的原因。阅读编辑后的版本似乎这只是“正常”的并发修改。

标签: java concurrentmodification


【解决方案1】:

基本问题是同步列表没有以有用的方式同步。

问题在于,虽然它的方法是同步的,但像移动元素这样应该是原子的动作却不是,因为移动所需的单独调用不是同步的一起。这意味着其他线程可以进入各个方法调用之间。因此,同步集合现在在很大程度上已被弃用。

尽管有这个缺陷,但如果您有另一个线程在您的线程排序时添加一个元素,您将收到此异常,因为排序迭代并且在迭代期间更改列表会导致异常。

幸运的是,JDK 有新的 Collection 类,它们具有工业强度(和有用的)同步,这要归功于 java.util.concurrent 包。

CopyOnWriteArrayList 替换你的列表,不要“同步”它,你会很高兴的。

【讨论】:

  • 你知道在这种情况下我不能使用Collections.sort
  • 它可以工作,但问题是“自创建迭代器以来,迭代器不会反映列表的添加、删除或更改”
  • “移动所需的单独调用不同步在一起” - 您可以通过在列表上直接同步的块中调用多个方法调用来同步多个方法调用。以下代码对列表进行排序,同时强制其他线程等到排序完成后再修改列表:synchronized (mGroupItemSampleList) { Collections.sort(mGroupItemSampleList, new GroupItemSampleComparator()); }
  • @Jack 但这正是 问题。如果你有一个“同步列表”,你不希望自己同步它!所以人们不这样做,并且不可避免地会出现错误。
  • @Bohemian 同意,我只想提一下可能在同步列表上原子地执行多个操作。书面答案表明这是不可能的,唯一的办法是使用不同的实现。
【解决方案2】:

Collections.synchronizedList(list)返回一个同步列表,表示该列表的方法将被同步(只能同时运行其中一个)。

然而,这并不意味着当其他人(或者也许你)正在使用它的迭代器迭代列表时,你不能调用列表的方法(iterator() 返回的迭代器不是同步)。 synchronizedList() 不会保护您免受 ConcurrentModificationException 的影响,如果有人正在迭代列表并且它以迭代器方法以外的任何其他方式进行修改。

编辑:

你的GroupItemSampleComparator 也不好,如果通过equals() 方法认为传递的2 个对象相等,它必须返回0。试试这个(假设getStartDate() 返回long):

public int compare(WmGroupItemSample s1, WmGroupItemSample s2) {
    long diff = s2.getStartDate() - s1.getStartDate();
    return diff > 0 ? -1 : diff < 0 ? 1 : 0;
}

【讨论】:

    【解决方案3】:

    也许这会有所帮助 - 没有看到所有代码并且有其他访问列表的机会。引用synchronizedList(List&lt;T&gt; list)上的Javadoc

    返回由指定列表支持的同步(线程安全)列表。为了保证串行访问,所有对backing list的访问都是通过返回的list来完成的。

    用户在迭代返回的列表时必须手动同步它:

    List list = Collections.synchronizedList(new ArrayList());
      ...
    synchronized (list) {
        Iterator i = list.iterator(); // Must be in synchronized block
        while (i.hasNext())
            foo(i.next());
    }
    

    那么,这个列表上的所有迭代都以这种方式保护吗?

    【讨论】:

      【解决方案4】:

      此异常不仅仅发生在多线程环境中。例如,如果您正在迭代列表并在迭代期间移除元素,则可能会发生此异常(取决于您移除该元素的方式)。

      【讨论】:

      • 我知道,但为什么 Exception 将我指向Collections.sort?我可以理解排序过程会删除和附加排序目标的项目。我怎样才能摆脱它?
      • @fessy 你能提供你的GroupItemSampleComparator吗?
      • 添加了 GroupItemSampleComparator 类
      猜你喜欢
      • 2014-06-19
      • 2018-10-21
      • 2020-12-15
      • 2013-02-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-03-08
      • 2016-03-08
      相关资源
      最近更新 更多