简体   繁体   English

在这种情况下如何处理ConcurrentModificationException

[英]How to deal with ConcurrentModificationException in this case

I'm working on a contour scanner. 我正在研究轮廓扫描仪。 For every contour I want to save the corners / edge coördinates. 对于每个轮廓,我想保存角落/边缘协调。 Instead of having an Array in the contour I have 1 big Array that I share. 而不是在轮廓中有一个数组,我有一个大的数组,我分享。 The reason for this is that it has to work on animations so this is for optimization. 这样做的原因是它必须处理动画,所以这是为了优化。

The idea is that every contour get's a subList view of the big array. 这个想法是每个轮廓得到一个大数组的子列表视图。

I worked quite long on a class to make that easy and now I run into a problem: 我在课堂上工作了很长时间才能轻松完成,现在我遇到了一个问题:

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++) {

}

First, is it poor java design that it does throws the concurrent modification if I use add(Object) ? 首先,如果我使用add(Object) ,它是否会引发并发修改,这是不好的java设计? This should have no influence on any subList I already have since it adds to the end right? 这应该对我已经拥有的任何子列表没有影响,因为它添加到右边? (logical speaking). (逻辑来说)。 My point is, add(Object) can never influence an already excisting subList, only add(index, Object) can do that (and other things like remove, swap and sort). 我的观点是, add(Object)永远不会影响已经被激活的subList,只有add(index, Object)可以做到这一点(以及其他东西,比如remove,swap和sort)。

Second. 第二。 What would be a good way to deal with this? 处理这个问题的好方法是什么? I could make the big array really big so it's less likely I need to add elements after I already made a subList but I wonder if there are any other good ways to deal with it. 我可以让大数组真的很大,所以我不太可能在我已经创建了一个子列表后添加元素,但我想知道是否还有其他好的方法来处理它。

Here is a class I made that had to make it easy for me but is making my life hard right now :) 这是我制作的课程,这对我来说很容易让我的生活变得艰难:)

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);
    }

}

This is a small example of how to use the class: 这是如何使用该类的一个小例子:

 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());

According to the javadocs for ArrayList.sublist : 根据ArrayList.sublist的javadocs:

"The semantics of the list returned by this method become undefined if the backing list (ie, this list) is structurally modified in any way other than via the returned list. (Structural modifications are those that change the size of this list, or otherwise perturb it in such a fashion that iterations in progress may yield incorrect results.)" “如果支持列表(即此列表)在结构上以除返回列表之外的任何方式进行修改,则此方法返回的列表的语义将变为未定义。(结构修改是更改此列表大小的修改,或者否则以这种方式扰乱它,以至于正在进行的迭代可能会产生不正确的结果。)“

In the implementation of ArrayList in the JVM that you are using, the "unspecified" semantics would appear to be that it throws a ConcurrentModificationException . 在您正在使用的JVM中实现ArrayList时,“未指定”的语义似乎是抛出ConcurrentModificationException


First, is it poor java design that it does throws the concurrent modification if I use add(Object)? 首先,如果我使用add(Object),它是否会引发并发修改,这是不好的java设计?

Nope. 不。 It is not "poor design". 这不是“糟糕的设计”。

They designed that way, deliberately, and for good reasons. 他们故意,有理由地设计了这种方式。 Furthermore, they documented that you would get unspecified behaviour if you did what you are doing. 此外,他们记录了如果你做了你正在做的事情,你会得到未指明的行为。 And indeed, they have implemented the sublist class to fast fail if it detects a concurrent modification. 实际上,如果检测到并发修改,他们已经实现了子列表类以快速失败 That is a good thing ... and entirely consistent with the API spec. 这是一件好事......并且完全符合API规范。

This should have no influence on any subList I already have since it adds to the end right? 这应该对我已经拥有的任何子列表没有影响,因为它添加到右边?

The problem is that while your example is (may be) safe, others are not. 问题在于,虽然你的例子(可能)是安全的,但其他人却不是。 And it is not possible to distinguish the safe and unsafe cases without adding a whole lot of additional infrastructure that would most likely make the ArrayList less efficient and more memory intensive for other use-cases. 并且,如果不添加大量额外的基础架构,就不可能区分安全和不安全的情况,这些基础架构最有可能使ArrayList效率降低,并且对其他用例的内存密集程度更高。

(Hence my "for good reason" comment above.) (因此我的“有充分理由”评论如上。)

Second. 第二。 What would be a good way to deal with this? 处理这个问题的好方法是什么?

It is hard to advise on that without understanding what your code actually needs to do. 如果不了解您的代码实际需要做什么,很难就此提出建议。 But some generic alternatives spring to mind: 但是我想到了一些通用的替代方案:

  • Change your algorithm so that you don't use the List.sublist method; 更改算法,以便不使用List.sublist方法; eg pass the first and last indexes of your notional sublists as parameters. 例如,将您的名义子列表的第一个和最后一个索引作为参数传递。

  • Recreate the sublist object each time you update the backing list. 每次更新支持列表时重新创建子列表对象。 (Assuming that your updates to the backing list are really safe, then this is simple, and trivially correct ... assuming that you are not in the middle of using the sublist's iterator.) (假设您对支持列表的更新非常安全,那么这很简单,并且非常正确...假设您没有使用子列表的迭代器。)

  • Change your code to use a Queue or Deque instead of a List . 更改您的代码以使用QueueDeque而不是List

  • Implement your own List class (based on ArrayList ) that doesn't throw CME's in this safe scenario. 实现您自己的List类(基于ArrayList ),该类在此安全方案中不会抛出CME。

The documentation for ArrayList's subList method indicates that sublists should only be used while the structure of the backing list does not change: ArrayList的subList方法的文档指示只应在后备列表的结构不更改时使用子列表:

The semantics of the list returned by this method become undefined if the backing list (ie, this list) is structurally modified in any way other than via the returned list. 如果支持列表(即此列表)在结构上以除返回列表之外的任何方式进行修改,则此方法返回的列表的语义将变为未定义。 (Structural modifications are those that change the size of this list, or otherwise perturb it in such a fashion that iterations in progress may yield incorrect results.) (结构修改是那些改变了这个列表的大小,或以其他方式扰乱它的方式,正在进行的迭代可能会产生不正确的结果。)

Although you're right that adding to the end of the backing list shouldn't cause any unexpected behavior in sublists, Java is being pessimistic in this case and throwing the ConcurrentModificationException if you use the sublist in any way after the backing list changes. 虽然你是正确的,添加到支持列表的末尾不应该在子列表中引起任何意外行为,但在这种情况下Java是悲观的,如果在支持列表更改后以任何方式使用子列表,则抛出ConcurrentModificationException This is similar to the fail-fast behavior of list iterators, which is also explained in the ArrayList docs , and it takes the view that it's better to fail cleanly rather than guess wrong about which concurrent modifications are "safe" and end up with unexpected behavior. 这类似于列表迭代器的失败快速行为,这也在ArrayList文档中进行解释,并且它认为最好是干净地失败而不是猜测哪些并发修改是“安全的”并最终导致意外行为。

The easiest way to get around this, of course, is to finish all of your structural modifications to the big ArrayList before you create the sublists. 当然,解决这个问题的最简单方法是在创建子列表之前完成对大型ArrayList的所有结构修改。 Is there any reason you need to interleave creating sublists with expanding the ArrayList? 是否有任何理由需要交错创建子列表和扩展ArrayList?

If you absolutely need to modify the big ArrayList while using subsections of it, I'd suggest not using subLists and just working directly with indices into the ArrayList. 如果你在使用它的子部分时绝对需要修改大型ArrayList,我建议不要使用子列表,只是直接使用索引到ArrayList中。 Each of your contour tasks would get the start and end indices of the subsection of the ArrayList it's working on, and it would go through them by repeatedly using get() on the big ArrayList. 每个轮廓任务都会获得它正在处理的ArrayList的子部分的开始和结束索引,并且它将通过在大型ArrayList上重复使用get()来完成它们。 Iterating using explicit get() calls is important here, since iterating with an iterator or for-each loop will run into the same ConcurrentModificationException problem when you add to the end of the big ArrayList. 使用显式get()调用进行迭代在这里很重要,因为当您添加到大型ArrayList的末尾时,使用迭代器或for-each循环进行迭代将遇到相同的ConcurrentModificationException问题。

Alternatively, if you really need a List -compatible view of the sublists for each contour tasks, you could implement your own SubList class that does not throw an exception if its backing list gets add() ed to, and then write a small subclass of ArrayList that overrides subList() to return this instead. 或者,如果您确实需要每个轮廓任务的子列表的List -compatible视图,您可以实现自己的SubList类,如果其后备列表add() ,则不会抛出异常,然后编写一个小的子类ArrayList ,它覆盖subList()以返回它。 The source code for Java's ArrayList and SubList is available online , so you can refer to it if you want to make sure your SubList does everything Java's does. Java的ArrayListSubList的源代码可以在线获得 ,因此如果你想确保你的SubList完成Java所做的一切,你可以参考它。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何处理ConcurrentModificationException - How to deal with ConcurrentModificationException 在这种情况下如何避免 ConcurrentModificationException? - How to Avoid ConcurrentModificationException in that case? 在执行swapCursor()时如何处理ConcurrentModificationException? - How to deal with ConcurrentModificationException while doing swapCursor()? 如何避免一次监听的情况下发生ConcurrentModificationException - how to avoid ConcurrentModificationException for listen once listener case 有什么简单的方法可以在此Java游戏中处理ConcurrentModificationException? - Any easy way to deal with ConcurrentModificationException in this java game? 处理List上的并发修改而没有ConcurrentModificationException - Deal with concurrent modification on List without having ConcurrentModificationException 在这种情况下,我应该如何处理异常? - In this case, how should I deal with exception? 如何处理Eclipse模板的Camel Case? - How to deal with Camel Case for Eclipse Templates? 如何处理测试用例中Set的随机顺序? - How to deal with random order of Set in test case? 如何在基于枚举的交换机中处理默认情况? - How to deal with default case in an enum based switch?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM