在这种情况下如何处理ConcurrentModificationException

时间:2014-12-03 03:42:58

标签: java list sublist

我正在研究轮廓扫描仪。对于每个轮廓,我想保存角落/边缘协调。 而不是在轮廓中有一个数组,我有一个大的数组,我分享。这样做的原因是它必须处理动画,因此这是为了优化。

这个想法是每个轮廓得到一个大数组的子列表视图。

我在课堂上工作了很长时间才能轻松完成,现在我遇到了一个问题:

ArrayList<PVector> vecs = new ArrayList<PVector>();

for (int i = 0; i < 10; i++) {
  vecs.add(new PVector());
}

List<PVector> subList = vecs.subList(0, 5);

for (int i = 0; i < 10; i++) {
  vecs.add(new PVector());
}

// ConcurrentModificationException
for (int i = 0; i < subList.size (); i++) {

}

首先,如果我使用add(Object),那么它的java设计是否会引发并发修改?这应该对我已经拥有的任何子列表没有影响,因为它添加到右边? (逻辑来说)。我的观点是,add(Object)永远不会影响已经存在的子列表,只有add(index, Object)可以做到(以及删除,交换和排序等其他内容)。

二。处理这个问题的好方法是什么?我可以让大数组真的很大,所以在我已经创建了一个子列表之后我不太可能需要添加元素,但我想知道是否还有其他好的方法来处理它。

这是我制作的课程,这对我来说很容易让我的生活变得艰难:)

public class ListDivisor<T> {

    List<T> list;

    int subListStartIndex = 0;
    int currentGetIndex = 0;

    InstanceHelper instanceHelper;


    public ListDivisor(List<T> list, InstanceHelper<T> instanceHelper) {
        this.list = list;
        this.instanceHelper = instanceHelper;
    }

    // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


    public void reset() {
        subListStartIndex = 0;
        currentGetIndex = 0;

        if (instanceHelper.doResetInstances()) {
            for (T obj : list) {
                instanceHelper.resetInstance(obj);
            }
        }
    }

    // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


    public List<T> getSubList(int size) {

        int fromIndex = subListStartIndex; // inclusive
        int toIndex = fromIndex + size; // exclusive

        for (int i = list.size(); i < toIndex; i++) {
            list.add((T) instanceHelper.createInstance());
        }

        subListStartIndex = toIndex;
        currentGetIndex = toIndex;

        return list.subList(fromIndex, toIndex);
    }

    // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

    /**
     * Returns a subList starting where the previous subList ended till
     * the latest object added till then.
     *
     * @return
     */
    public List<T> getSubList() {
        return getSubList(currentGetIndex-subListStartIndex);
    }

    // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


    public T getNext() {

        if (currentGetIndex >= list.size()) {
           list.add((T)instanceHelper.createInstance());
        }

        return list.get(currentGetIndex++);

    }

    // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .


    public void clear() {
        list.clear();
        reset();
    }


    // . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

    public interface InstanceHelper<T> {
        public T createInstance();
        public boolean doResetInstances();
        public void resetInstance(T obj);
    }

}

这是如何使用该类的一个小例子:

 ListDivisor<PVector> vectorsDivisor = new ListDivisor<PVector>(
     new ArrayList<PVector>(), 
     new InstanceHelper<PVector>() {
            //@Override
            public PVector createInstance() {
                return new PVector();
            }

            //@Override
            public boolean doResetInstances() {
              return true;
            }

            //@Override
            public void resetInstance(PVector v) {
                 v.set(0,0,0);              
            }
     });

  PVector v = vectorsDivisor.getNext();
  v.set(1, 1, 1);

  v = vectorsDivisor.getNext();
  v.set(2, 2, 2);

  v = vectorsDivisor.getNext();
  v.set(3, 3, 3);

  subList = vectorsDivisor.getSubList();


  println("subList size: "+subList.size());

2 个答案:

答案 0 :(得分:2)

根据ArrayList.sublist的javadoc:

  

“如果支持列表(即此列表)在结构上以除返回列表之外的任何方式进行修改,则此方法返回的列表的语义将变为未定义。(结构修改是那些更改此列表大小的修改,或以其他方式扰乱它,使得正在进行的迭代可能产生不正确的结果。)“

在您正在使用的JVM中的ArrayList实现中,“未指定”的语义似乎是抛出ConcurrentModificationException


  

首先,如果我使用add(Object),那么它的java设计是否会引发并发修改?

不。这不是“糟糕的设计”。

他们是故意设计的,并且有充分的理由。此外,他们记录,如果你做了你正在做的事情,你会得到未指明的行为。事实上,如果检测到并发修改,他们已经将子列表类实现为快速失败。这是一件好事......并且完全符合API规范。

  

这应该对我已经拥有的任何子列表没有影响,因为它添加到右边?

问题在于,虽然你的例子(可能)是安全的,但其他人却不是。如果不添加大量额外的基础架构,很可能会使安全和不安全案例区分开来,这些基础架构最有可能使ArrayList效率降低,而且对其他用例的内存密集程度更高。

(因此我的“有充分理由”的评论如上。)

  

二。处理这个问题的好方法是什么?

如果不了解您的代码实际需要做什么,很难就此提出建议。但是我想到了一些通用的替代方案:

  • 更改算法,以便不使用List.sublist方法;例如将您的名义子列表的第一个和最后一个索引作为参数传递。

  • 每次更新支持列表时重新创建子列表对象。 (假设您对支持列表的更新非常安全,那么这很简单,而且非常正确......假设您没有使用子列表的迭代器。)

  • 将您的代码更改为使用QueueDeque代替List

  • 实现您自己的List类(基于ArrayList),在此安全方案中不会抛出CME。

答案 1 :(得分:1)

ArrayList的subList方法的documentation表示只应在后备列表的结构不变的情况下使用子列表:

  

如果支持列表(即此列表)在结构上以除返回列表之外的任何方式进行修改,则此方法返回的列表的语义将变为未定义。 (结构修改是那些改变了这个列表的大小,或以其他方式扰乱它的方式,正在进行的迭代可能会产生不正确的结果。)

虽然你是对的,但是添加到支持列表的末尾不应该导致子列表中的任何意外行为,在这种情况下Java是悲观的,如果你使用的话,抛出ConcurrentModificationException支持列表更改后以任何方式的子列表。这类似于列表迭代器的快速失败行为,这也在the ArrayList docs中进行了解释,它认为最好是干净地失败而不是猜测哪些并发修改是&# 34;安全&#34;并最终出现意外行为。

当然,解决这个问题的最简单方法是在创建子列表之前完成对大型ArrayList的所有结构修改。您是否有任何理由需要交换创建子列表和扩展ArrayList?

如果你在使用它的子部分时绝对需要修改大型ArrayList,我建议不要使用子列表,只是直接使用索引到ArrayList中。每个轮廓任务都会获得它正在处理的ArrayList的子部分的开始和结束索引,并且它将通过在大型ArrayList上重复使用get()来完成它们。使用显式get()调用进行迭代非常重要,因为当您添加到大型ArrayList的末尾时,使用迭代器或for-each循环进行迭代将遇到相同的ConcurrentModificationException问题。

或者,如果您确实需要每个轮廓任务的子列表的List - 兼容视图,您可以实现自己的SubList类,如果其后备列表得到add(),则不会抛出异常to,然后编写一个覆盖ArrayList的{​​{1}}的小子类来返回它。 Java subList()ArrayList的源代码是available online,因此如果您想确保您的SubList完成Java所做的一切,您可以参考它。