【问题标题】:ConcurrentModificationException on list that is not being modified未修改的列表上的 ConcurrentModificationException
【发布时间】:2017-03-27 13:24:30
【问题描述】:

编辑:问题解决了!

感谢大家的帮助。我知道我需要同步一组,只是不知道是哪一组。相当尴尬的是,我同步了正确的,但没有将同步集分配给变量。哦,好吧,我们生活和学习。

我对 Java 比较陌生,这是我的第一个个人项目。我正在尝试构建一个简单的 2d 平台游戏,并且有一个我无法弄清楚的 ConcurrentModificationException 弹出窗口。我已经搜索了一段时间,但找不到与我的问题完全一样的人。问题出现在以下方法中:

public Set<Platform> change(Set<Platform> platforms)
{
    Set<Platform> viewSet = new HashSet<Platform>();
    Point abPos = ball.getAbPos();
    int viewWidth, viewHeight, viewXPos, viewYPos;
    for (Platform p : platforms) {
        if (null != p.getType()) {
            switch (p.getType()) {
                case PLATFORM:{
                    viewWidth = p.getWidth() * xRatio;
                    viewHeight = p.getHeight() * yRatio;
                    viewXPos = (p.getX() - ball.getX()) * xRatio + abPos.x;
                    viewYPos = (p.getY() - ball.getY()) * yRatio + abPos.y;
                    viewSet.add(new Platform(viewXPos, viewYPos, viewWidth, viewHeight, p.getType()));
                    break;
                }
                case WALL:{
                    viewWidth = p.getWidth() * xRatio;
                    viewHeight = p.getHeight() * yRatio;
                    viewXPos = (p.getX() - ball.getX()) * xRatio + abPos.x;
                    viewYPos = (p.getY() - ball.getY()) * yRatio + abPos.y;
                    viewSet.add(new Platform(viewXPos, viewYPos, viewWidth, viewHeight, p.getType()));
                    break;
                }
                case DROP:
                    break;
                default:
                    break;
            }
        } else {
        }
    }
    return viewSet;
}

该方法在 JPanel 的 paintComponent() 方法中调用,它来自我创建的 Camera 对象,他的工作是获取该方法传递给它的对象列表并调整它们的大小以进行绘制。这在游戏的每次迭代中都会发生一次。传递给方法的对象列表在每次迭代时从到玩家的设定距离处收集,返回的调整大小的对象列表由 paintComponent() 方法绘制。

问题在于,当我启动 for 循环以遍历传递给该方法的对象列表时,change() 方法通常会抛出 ConcurrentModificationException,尽管并非每次迭代都如此。我真的不知道为什么抛出异常的列表没有以任何方式修改。当我开始拔头发时,任何帮助都将不胜感激。我尝试过同步各种方法或集合,但这似乎没有任何区别,除了有时会导致代码滞后。

我得到的错误如下:

    Exception in thread "AWT-EventQueue-0" java.util.ConcurrentModificationException
at java.util.HashMap$HashIterator.nextNode(HashMap.java:1429)
at java.util.HashMap$KeyIterator.next(HashMap.java:1453)
at mygame_2.Camera.change(Camera.java:39)
at mygame_2.Game2Window$DrawingPanel.paintComponent(Game2Window.java:237)
at javax.swing.JComponent.paint(JComponent.java:1056)
at javax.swing.JComponent.paintChildren(JComponent.java:889)
at javax.swing.JComponent.paint(JComponent.java:1065)
at javax.swing.JComponent.paintChildren(JComponent.java:889)
at javax.swing.JComponent.paint(JComponent.java:1065)
at javax.swing.JLayeredPane.paint(JLayeredPane.java:586)
at javax.swing.JComponent.paintChildren(JComponent.java:889)
at javax.swing.JComponent.paintToOffscreen(JComponent.java:5217)
at javax.swing.RepaintManager$PaintManager.paintDoubleBuffered(RepaintManager.java:1579)
at javax.swing.RepaintManager$PaintManager.paint(RepaintManager.java:1502)
at javax.swing.RepaintManager.paint(RepaintManager.java:1272)
at javax.swing.JComponent.paint(JComponent.java:1042)
at java.awt.GraphicsCallback$PaintCallback.run(GraphicsCallback.java:39)
at sun.awt.SunGraphicsCallback.runOneComponent(SunGraphicsCallback.java:79)
at sun.awt.SunGraphicsCallback.runComponents(SunGraphicsCallback.java:116)
at java.awt.Container.paint(Container.java:1975)
at java.awt.Window.paint(Window.java:3912)
at javax.swing.RepaintManager$4.run(RepaintManager.java:842)
at javax.swing.RepaintManager$4.run(RepaintManager.java:814)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:76)
at javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:814)
at javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:789)
at javax.swing.RepaintManager.prePaintDirtyRegions(RepaintManager.java:738)
at javax.swing.RepaintManager.access$1200(RepaintManager.java:64)
at javax.swing.RepaintManager$ProcessingRunnable.run(RepaintManager.java:1732)
at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:311)
at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:756)
at java.awt.EventQueue.access$500(EventQueue.java:97)
at java.awt.EventQueue$3.run(EventQueue.java:709)
at java.awt.EventQueue$3.run(EventQueue.java:703)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:76)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:726)
at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:201)
at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:116)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:105)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:93)
at java.awt.EventDispatchThread.run(EventDispatchThread.java:82)

抱歉,如果代码或这个问题是混乱的。

【问题讨论】:

  • change 在 AWT 线程而不是程序主线程中调用。可能存在错误的同步问题。简单的答案是将集合与Collections.sychronizedSet() 同步。
  • 某处,变量platforms 所指的集合正在被修改,而change 方法正在对其进行迭代;在您没有向我们展示的代码中。它可能发生在不同的线程上。
  • 正如之前的评论者所说,您的输入状态已被修改,而Camera.change 方法对其进行了迭代。然而,可能有另一种方法可以在不使用synchronizedSet 的情况下修复它,因为使用它可能会导致速度变慢,因为它会创建一个等待者队列来修改集合,并且仍然可能导致 CME。您可以尝试为您的platforms 查看java.util.concurrent.CopyOnWriteArraySet 课程
  • 使用CopyOnWrite... 集合可以让您的迭代继续进行,即使该集合在别处被修改,但它有其成本。首先,迭代不会“看到”任何在它进行时完成的更新——你只能看到在你开始迭代之前的集合。此外,每次更新都会在内部有效地添加另一组,因此添加速度可能会更慢并消耗您的内存。
  • 我现在已经解决了这个问题,但是出于对 glee8e 的兴趣,我的大部分游戏代码都在扩展的 JFrame 类中。将代码放在主类中会更好吗?目前所有主要的工作就是创建游戏窗口并告诉它开始运行。

标签: java


【解决方案1】:

变量viewSet是本地的,所以不会抛出异常,所以排除不可能,唯一能抛出这个异常的集合就是你传入的那个。

你传入的Set 一定来自某个地方,并且一定有什么东西在填充它。由于paintComponent 发生在 Swing 渲染线程上,冲突可能是您在 Swing 线程上读取 Set,同时在另一个线程上对其进行修改。

如果您在 synchronized 块内修改此集合,并在 synchronized 块中遍历 Set,问题应该会消失:

synchronized (set) {
    // read/write here
}

变量set 在这两种情况下应该是相同的集合。

【讨论】:

  • 在这样的集合上同步可能意味着,然而,这个集合的更新将总是等待迭代完成——事实上,其中一个可能永远不会通过。据我所知,没有完美的答案,这取决于布局的其余部分:与迭代相比,更新发生的速度有多快,迭代是否可以停止所有更新,等等。
  • 当然可以,但是没有足够的信息来进行评估。虽然迭代看起来相当快(毕竟它是从paintComponent 调用的)所以我认为这种简单的方法在这里可以工作。在这个阶段建议使用并发集合似乎有点过头了。
【解决方案2】:

我只在使用 for-each 循环时遇到过这个错误。尝试将 for-each 循环切换为传统的 for 循环。它应该消除错误。

【讨论】:

  • 在这种情况下,循环样式无关紧要,除非存在检查并发修改的错误。
猜你喜欢
  • 1970-01-01
  • 2022-01-24
  • 1970-01-01
  • 1970-01-01
  • 2013-11-12
  • 2015-06-04
  • 2015-11-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多