【问题标题】:ConcurrentModificationException Occuring When Retrieving List Size检索列表大小时发生 ConcurrentModificationException
【发布时间】:2017-03-28 07:36:25
【问题描述】:

对于我的数据结构类中的一个项目,我的任务是创建一个 3 维范围树,其中每个维度都是一个 BST。我看了this question,但这是一个Android问题,我们问题的原因似乎不同,唯一的答案没有被接受。

代码墙即将推出。对不起。

涉及的课程:

  • Point3D<T>:包含点坐标的通用类。 T extends Comparable<T>Point3D 也对其进行了扩展
  • RangeTree<T>:包含整个树的根以及构建和搜索树的方法的泛型类

除了这 2 个类之外,还有更多类,但它们似乎与我收到 ConcurrentModificationException (link to CME) 的原因无关。 这是我运行驱动程序时得到的结果:

Read in 10 points //Number of points read in driver, this is okay
5//first median, 10 / 2
2//second median 5 / 2
first[0.082  0.791  0.832  , 0.366  0.136  0.009  ]//correct
second[0.138  0.968  0.391  , 0.414  0.785  0.883  , 0.546  0.044  0.133  ]//correct
merged[0.082  0.791  0.832  , 0.366  0.136  0.009  ]//not correct
first[0.082  0.791  0.832  , 0.366  0.136  0.009  ]//printed again. why?
2//secondHalf being sorted
first[0.415  0.64  0.099  , 0.513  0.612  0.466  ]//correct
second[0.091  0.719  0.772  , 0.199  0.999  0.086  , 0.896  0.835  0.86  ]//correct
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$SubList.checkForComodification(ArrayList.java:1231)
at java.util.ArrayList$SubList.size(ArrayList.java:1040)
at RangeTree.mergeList(RangeTree.java:189)
at RangeTree.sortAscending(RangeTree.java:175)
at RangeTree.sortAscending(RangeTree.java:173)
at RangeTree.build3DTree(RangeTree.java:126)
at RangeTree.build(RangeTree.java:11)
at Main.main(Main.java:35)

RangeTree.build 被调用(Main.java:35) 时,它需要一个List<Point3D<T>> 并将其传递给RangeTree.build3DTree(RangeTree.java:11),它还需要# 个维度来构建,以及一个参数指示List 是否已经排序。 RangeTree.java:126 在那个方法中是我在build3DTree 中调用sortAscending 的那一行。

sortAscending(它是递归的)通过比较某个维度上的点来实现听起来的效果,从x(x = 0, y = 1, z = 2) 开始,如果它们相等,它会检查下一个维度等。基于上述内容,它显然工作正常,除了Line 172 以某种方式打印两次的奇怪故障。下面是sortAscending 代码(所有打印行纯粹是为了调试):

private List<Point3D<T>> sortAscending(List<Point3D<T>> pointArr){

    if(pointArr.size() <= 3){//minimum sublist size is 3
        int median = findMedian(pointArr);
        Point3D<T> medianElement = pointArr.get(median);//retrieves coordinate to sort on
        int compareLeft = medianElement.compareTo(pointArr.get(median - 1));
        if(compareLeft < 0){//determine if median is less than element to its left. There will always be an element to the left
            pointArr.add(0, pointArr.remove(median));
            medianElement = pointArr.get(median);
        }
        try{//check median against element to its right. May not be valid if sublist is size 2
            int compareRight = medianElement.compareTo(pointArr.get(median + 1));
            if(compareRight > 0){
                Point3D<T> rightEnd = pointArr.get(median + 1);
                Point3D<T> leftEnd = pointArr.get(median - 1);
                if(rightEnd.compareTo(leftEnd) < 0)
                    pointArr.add(0, pointArr.remove(median + 1));
                else
                    pointArr.add(median, pointArr.remove(median + 1));
            }
        } catch (IndexOutOfBoundsException e){

        }
        return pointArr;
    }
    int median = findMedian(pointArr);//returns pointArr.size() / 2
    System.out.println(median);//for debugging
    List<Point3D<T>> firstHalf = sortAscending(pointArr.subList(0, median));
    System.out.println("first" + firstHalf); //prints twice, once when it should, and again after Line 176 prints
    List<Point3D<T>> secondHalf = sortAscending(pointArr.subList(median, pointArr.size()));//Line 173
    System.out.println("second" + secondHalf);//debugging
    List<Point3D<T>> merged = mergeList(firstHalf, secondHalf);//Line 175
    System.out.println("merged" + merged);//debugging
    return merged;//mergeList(firstHalf, secondHalf);
}

所以,CME 的最终来源似乎在mergeList,直到调用后半部分数据才中断。 mergeList(迭代)取两个List&lt;Point3D&lt;T&gt;&gt;并将它们合并到一个按升序排序的列表中,然后返回它。即,它接受第一个参数并将其修改为合并列表并返回它。代码:

private List<Point3D<T>> mergeList(List<Point3D<T>> firstList, List<Point3D<T>> secList){
    int sListSize = secList.size();
    int fListSize = firstList.size();//Line 188
    Point3D<T> firstListElement = firstList.get(fListSize - 1);
    Point3D<T> secListElement = secList.get(0);

    if(secListElement.compareTo(firstListElement) >= 0){//check to see if secList can be appended to end of firstList e.g. firstList = {1, 2} secList = {3, 4, 5}
        firstList.addAll(secList);
        return firstList;
    }

    secListElement = secList.get(secList.size() - 1);
    firstListElement = firstList.get(0);

    if(secListElement.compareTo(firstListElement) <= 0){//check to see if firstList can be appended to the end of secList e.g. secList = {1, 2} firstList = {3,4,5}
        secList.addAll(firstList);
        return secList;
    }

    for(int a = 0; a < secList.size(); a++){//take an element from secList and insert it into firstList
        secListElement = secList.get(a);
        int minIdx, maxIdx, median = findMedian(firstList);
        int mComp = secListElement.compareTo(firstList.get(median));//compare to the median of firstList to choose side to start from
        if(mComp < 0){
            minIdx = 0;
            maxIdx = median;
        }
        else if(mComp > 0){
            minIdx = median;
            maxIdx = firstList.size();
        }
        else{//if mComp is 0, insert it at median index
            firstList.add(median, secList.get(a));
            continue;
        }

        for(; minIdx < maxIdx; minIdx++){
            firstListElement = firstList.get(minIdx);
            int compare = secListElement.compareTo(firstListElement);
            if(compare <= 0){
                firstList.add(minIdx, secList.get(a));
                break;
            }
        }
    }
    return firstList;
}

因此,由于某种原因,当我尝试访问 firstList 的大小时,CME 出现了。我不知道为什么会这样。我已经使用数据集(发布在下面)手动跟踪了我的代码通过每个步骤,但我看不到 CME 的来源。数据:

10
0.366   0.136   0.009
0.082   0.791   0.832//beautiful 3D points
0.138   0.968   0.391
0.414   0.785   0.883
0.546   0.044   0.133
0.513   0.612   0.466
0.415   0.640   0.099
0.199   0.999   0.086
0.896   0.835   0.860
0.091   0.719   0.772
0.25 0.75 0.25 0.75 0.25 0.75//range queries. Code fails before we get to this
0.75 0.25 0.8 0.1 0.9 0.1
0.95 1.75 0.1 0.01 0.1 0.2
exit//no more queries

【问题讨论】:

  • 仅作记录:考虑阅读 Robert Martin 的“清洁代码”。你的代码没那么可怕;但它也不是很好读。许多可以改进的细微之处......通常易于阅读的代码也更容易发现错误......
  • 你的主要功能在哪里?
  • @GhostCat 这就是我认为它应该基于文档的内容。但是当我把它全部画出来时,递归排序调用终止并且它们返回的列表直到合并时间才被触及。此外,数据的前半部分毫无例外地合并(显然它没有正确合并),后半部分在我访问不再更改或迭代的列表的大小时中断。
  • 你的堆栈跟踪说:在 RangeTree.build(RangeTree.java:11) 在 Main.main(Main.java:35) 所以我们应该看看这里发生了什么:)
  • @muzzlator 在我的驱动程序中,但这无关紧要。驱动程序中堆栈跟踪起源的行是 RangeTree 中的 build() 方法,它跟踪我发布的方法。

标签: java list binary-search-tree concurrentmodification


【解决方案1】:

问题在于函数List#subList 没有返回允许您修改的列表的副本。您需要制作这些列表的可变副本。不完全确定为什么只在size() 中进行并发检查,但请查看ConcurrentModificationException thrown by sublist 进行讨论

编辑:检查仅发生在size() 中的可能原因:允许您进行不会弄乱子列表结构的更改,例如与Collections.swap 的交换操作。他们必须只在size() 中进行检查,以允许实施这些“结构保留”操作而不会立即崩溃,因此您对add() 的调用可能会被搁置。

编辑 2:解决此问题的一种方法可能是始终按原样传递原始列表,并使用索引为您的递归定义子列表范围

【讨论】:

  • 这可能解释了为什么第一次合并尝试不起作用,子列表无法在结构上进行编辑。在阅读 List API 时,我没有意识到情况就是如此。我会试试看的。
【解决方案2】:

我会将这个问题归类为过度可变性,你开始处理一个列表,然后创建这个列表的视图,将元素添加到其中一个视图,然后合并,所有这些都在递归中。这是错误的秘诀。

我认为可以调试和解决您的问题,但它不能解决主要问题,即具有副作用的递归函数。

我会更改排序和合并以使用不可变参数,不要更改输入列表,创建新列表。

【讨论】:

    猜你喜欢
    • 2014-06-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-01-26
    • 2015-10-24
    • 1970-01-01
    • 2017-06-17
    • 1970-01-01
    相关资源
    最近更新 更多