【发布时间】: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