Java 可观测的并发修改异常

Java 可观测的并发修改异常,java,observable,Java,Observable,我对观察者设计模式有问题 我试着自己实现一些类似于观察者的东西,但遇到了一个我不理解的严重错误 java.util.ConcurrentModificationException at java.util.ArrayList$Itr.checkForComodification(Unknown Source) at java.util.ArrayList$Itr.next(Unknown Source) at userInterface.PauseScreen.acknowledgeChange

我对观察者设计模式有问题

我试着自己实现一些类似于观察者的东西,但遇到了一个我不理解的严重错误

java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(Unknown Source)
at java.util.ArrayList$Itr.next(Unknown Source)
at userInterface.PauseScreen.acknowledgeChanges(PauseScreen.java:57)
at userInterface.PauseScreen$1.getPressed(PauseScreen.java:22)
at evo.EvoMain.update(EvoMain.java:76)
at org.newdawn.slick.GameContainer.updateAndRender(GameContainer.java:646)
at org.newdawn.slick.AppGameContainer.gameLoop(AppGameContainer.java:412)
at org.newdawn.slick.AppGameContainer.start(AppGameContainer.java:322)
at evo.EvoMain.main(EvoMain.java:32)
Sat May 26 17:28:31 CEST 2018 ERROR:Game.update() failure - check the game code.
org.newdawn.slick.SlickException: Game.update() failure - check the game code.
at org.newdawn.slick.GameContainer.updateAndRender(GameContainer.java:663)
at org.newdawn.slick.AppGameContainer.gameLoop(AppGameContainer.java:412)
at org.newdawn.slick.AppGameContainer.start(AppGameContainer.java:322)
at evo.EvoMain.main(EvoMain.java:32)
背景:我正在用Slick2D用java编程一个游戏,它有点像暗黑破坏神。当我听说observer设计模式时,我很兴奋,并尝试将其应用到我的游戏中,这样不同的组件就可以轻松地相互通信。 我希望暂停屏幕与游戏的主类进行通信。 我还希望主类与玩家的状态屏幕进行通信。 这两个通信路径主要是告诉这些组件输入了什么,并获取他们被按下的按钮作为反馈。我还希望statscreen能够与球员沟通。第一:我希望统计屏幕告诉玩家他应该升级哪个统计,第二:我希望玩家将他的统计(hp、hp再生、耐力、耐力、dmg)返回统计屏幕,以便用户可以在那里查看并读取数字。我想这可以在没有观察器的情况下实现,但我遇到了一些困难,我认为从长远来看,实现观察器并保持对象的独立性会更好

因此,要向您展示一些图片:

此时,用户按下开始键,游戏初始化。 怪物和玩家都被加载了,其他的都被加载了

不幸的是,当“开始”按钮试图告诉游戏它被按下时,会产生wierd ConcurrentModificationError。更让我感到奇怪的是,错误只会发生,如果我将玩家添加到他/她的statScreen中作为一个侦听器,这对我来说毫无意义,它看起来是如此无关

编辑:有时,当我在发生错误的代码行上放置断点时,该行在异常出现之前被触发3次。在前两次突破中,观察家ArrayList包含EvoMain,但在第三次突破中,它也包含玩家。为什么呢? 当我删除上面提到的代码行时 玩家未出现在ArrayList中,游戏正常启动。 是否有某种错误导致pauseScreen和statScreen对象之间共享ArrayList?好像是静止的?! 结束编辑

为了隔离错误代码,我从这个版本的游戏中删除了大部分实际的游戏内容。 以下是我的课程:

埃沃曼:

package evo;

import java.util.Hashtable;
import java.util.Map;
import org.lwjgl.input.Mouse;
import org.newdawn.slick.*;
import userInterface.EPauseScreenMessage;
import userInterface.PauseScreen;
import userInterface.StatScreen;

public class EvoMain extends BasicGame implements Observer {

private static Map<String, Image> imageCatalogue = new Hashtable<String, Image>();
private static Player player = null;
private static PauseScreen pauseScreen = null;
private static StatScreen statScreen = null;
private static GameContainer container;

private static int state = 0;   // 0 = paused, 1 = running

public EvoMain() throws SlickException {
    super("Evo");
}

public static void main(String[] args) throws SlickException {
    AppGameContainer container = new AppGameContainer(new EvoMain());
    container.setDisplayMode(1200, 900, false);
    container.start();
}

@Override
public void render(GameContainer container, Graphics g) throws SlickException {
    if (player != null) {
        player.render(container, g);
        statScreen.render(container, g);
    }
    pauseScreen.render(container, g);
}

@Override
public void init(final GameContainer container) throws SlickException {
    fillImageCatalogue();
    EvoMain.container = container;
    pauseScreen = new PauseScreen(200, 100);
    pauseScreen.add(this);
    pauseScreen.setVisible(true);
    pauseScreen.setEnabled(true);
    statScreen = new StatScreen(0, 0, imageCatalogue.get("PlusButton"));
    statScreen.add(this);
    container.setMinimumLogicUpdateInterval(55);
    container.setMaximumLogicUpdateInterval(55);
}

@Override
public void update(GameContainer container, int delta) throws SlickException {
    Input input = container.getInput();
    switch (state) {
    case 0:
        if(input.isKeyPressed(Input.KEY_ESCAPE)){
            resumeGame();
            break;
        }
        if (input.isKeyPressed(Input.KEY_ENTER)) {
            resumeGame();
            break;
        }
        if (input.isMouseButtonDown(0)) {
            if (pauseScreen.isVisible() && pauseScreen.isEnabled()) {
                for (Button button : pauseScreen.getButtons()) {
                    if (button.getRect().contains(Mouse.getX(), container.getHeight() - Mouse.getY())) {
                        // for some weird reason Mouse.getY() always gives the container height - the mouse' y coord so I have to reverse that by doing the same
                        button.getPressed();
                    }
                }
            }
        }
        break;  // end of pause state
    case 1:
        if(input.isKeyPressed(Input.KEY_ESCAPE)){
            state = 0;
            pauseScreen.setVisible(true);
            pauseScreen.setEnabled(true);
            break;
        }
        if(input.isKeyPressed(Input.KEY_T)){
            if (statScreen.isEnabled()) {
                statScreen.setEnabled(false);
                statScreen.setVisible(false);
            } else {
                statScreen.setVisible(true);
                statScreen.setEnabled(true);
            }
        }
        int dX = -1; // player destination X
        int dY = -1; // player destination Y
        if (input.isMouseButtonDown(0)) {
            dX = Mouse.getX();
            dY = container.getHeight() - Mouse.getY();
            if (statScreen.isVisible() && statScreen.isEnabled()) {
                for (Button button : statScreen.getButtons()) {
                    if (button.getRect().contains(dX, dY)) {
                        button.getPressed();
                    }
                }
            }
        }
        if (player != null) {
            player.setDestination(dX, dY);
        }
        break; // end of running state
    }
}
public void resumeGame() {
    state = 1;
    pauseScreen.setVisible(false);
    pauseScreen.setEnabled(false);
}

public static void fillImageCatalogue() throws SlickException {
    imageCatalogue.put("PlusButton"         , new Image("res/PlusButton.jpg"));
}

public void initializeGame()  {
    EvoMain.player = new Player((float) 100, (float) 100);
    statScreen.add(EvoMain.player);
    // this line throws an exception in PauseScreen acknowledgeChanges();
}
public Player getPlayer() {
    return player;
}
public void setPlayer(Player player) {
    EvoMain.player = player;
}
@Override
public void update(Observable subject, Object object) {
    if (subject instanceof PauseScreen) {
        switch ((EPauseScreenMessage) object) {
        case EXIT:
            container.exit();
            break;
        case RESUME:
            resumeGame();
            break;
        case START:
            initializeGame();
            resumeGame();
        default:
            break;
        }
    } else
    if (subject instanceof StatScreen) {
        if (object == null) {
            statScreen.setVisible(false);
            statScreen.setEnabled(false);
        }
    }
}
}
可观测界面

package evo;

import java.util.ArrayList;

public interface Observable {

ArrayList<Observer> observers = new ArrayList<Observer>();

public void acknowledgeChanges();
public void add(Observer newObs);
public void remove(Observer actObs);
}
StatScreen类

package userInterface;

import org.newdawn.slick.Color;
import org.newdawn.slick.Image;
import org.newdawn.slick.geom.Rectangle;
import evo.Observable;
import evo.Observer;
import evo.Player;
import evo.PlayerMessage;

public class StatScreen extends UserInterface implements Observer, Observable{

String stat = "1";

public StatScreen(float x, float y, Image plusButtonImage) {
    super(x, y, 500, 550);
    mainColor   = new Color(200, 200, 200);
    borderColor = new Color(150, 150, 150);
    TextButton btnClose = new TextButton(getX() + 280, getY() + 460, new Rectangle(getX() + 280, getY() + 460, 200, 75), "Close") {
        @Override
        public void getPressed() {
            if (isEnabled()) {
                stat = "2";
                acknowledgeChanges();
            }
        }
    };
    this.add(btnClose);
}

@Override
public void acknowledgeChanges() {
    for (Observer observer : observers) {
        observer.update(this, stat);
    }
}

@Override
public void add(Observer newObs) {
    observers.add(newObs);
}

@Override
public void remove(Observer actObs) {
    observers.remove(actObs);
}

@Override
public void update(Observable subject, Object object) {
    if (subject instanceof Player) {
        PlayerMessage pM = (PlayerMessage) object;
        switch (pM.getStat()) {
        default:
            break;
        }
    }
}
public String prepareValue(double value) {
    return (int) value + "." + (int) (value * 10);
}
}
类用户接口

package userInterface;

import java.util.ArrayList;

import org.newdawn.slick.Color;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.SlickException;
import evo.Button;
import evo.EvoObject;

public abstract class UserInterface extends EvoObject {

boolean visible = false;
boolean enabled = false;
int width;
int height;
Color mainColor;
Color borderColor;
ArrayList<Button>   buttons = new ArrayList<Button>();

public UserInterface(float x, float y, int width, int height) {
    super(x, y);
    this.width = width;
    this.height = height;
}

public void render(GameContainer container, Graphics g) throws SlickException {
    if (visible) {  // render menu if visible
        g.setColor(mainColor);
        g.fillRect(getX(), getY(), this.width, this.height);
        g.setColor(borderColor);
        g.setLineWidth(3);
        g.drawRect(getX(), getY(), this.width, this.height);
        for (Button button : buttons) { // render all buttons
            button.render(container, g);
        }
    }
}

public boolean isVisible() {
    return visible;
}
public void setVisible(boolean visible) {
    this.visible = visible;
}
public boolean isEnabled() {
    return enabled;
}
public void setEnabled(boolean enabled) {
    this.enabled = enabled;
}
public int getWidth() {
    return width;
}
public void setWidth(int width) {
    this.width = width;
}
public int getHeight() {
    return height;
}
public void setHeight(int height) {
    this.height = height;
}
public Color getMainColor() {
    return mainColor;
}
public void setMainColor(Color mainColor) {
    this.mainColor = mainColor;
}
public Color getBorderColor() {
    return borderColor;
}
public void setBorderColor(Color borderColor) {
    this.borderColor = borderColor;
}
public void add(Button button) {
    buttons.add(button);
}
public ArrayList<Button> getButtons() {
    return buttons;
}
}
包用户界面;
导入java.util.ArrayList;
导入org.newdawn.slick.Color;
导入org.newdawn.slick.GameContainer;
导入org.newdawn.slick.Graphics;
导入org.newdawn.slick.SlickException;
导入evo.按钮;
导入evo.evo对象;
公共抽象类用户接口扩展了对象{
布尔可见=假;
布尔启用=假;
整数宽度;
内部高度;
主色;
颜色边界颜色;
ArrayList按钮=新建ArrayList();
公共用户界面(浮点x、浮点y、整数宽度、整数高度){
super(x,y);
这个。宽度=宽度;
高度=高度;
}
公共void渲染(GameContainer容器,图形g)引发异常{
如果(可见){//如果可见,则渲染菜单
g、 设置颜色(主颜色);
g、 fillRect(getX(),getY(),this.width,this.height);
g、 setColor(borderColor);
g、 设置线宽(3);
g、 drawRect(getX(),getY(),this.width,this.height);
对于(按钮:按钮){//渲染所有按钮
按钮。渲染(容器,g);
}
}
}
公共布尔值可见(){
返回可见;
}
公共void集合可见(布尔可见){
可见的;可见的;
}
公共布尔值isEnabled(){
返回启用;
}
已启用公共void集(已启用布尔值){
this.enabled=已启用;
}
公共int getWidth(){
返回宽度;
}
公共void setWidth(int-width){
这个。宽度=宽度;
}
公共整数getHeight(){
返回高度;
}
公共空间设置高度(内部高度){
高度=高度;
}
公共颜色getMainColor(){
返回主色;
}
公共void setMainColor(颜色mainColor){
this.mainColor=mainColor;
}
公共颜色getBorderColor(){
返回边框颜色;
}
公共颜色(颜色边框颜色){
this.borderColor=borderColor;
}
公共作废添加(按钮){
按钮。添加(按钮);
}
公共阵列列表getButtons(){
返回按钮;
}
}

错误使用接口导致的问题

public interface Observable {

    ArrayList<Observer> observers = new ArrayList<Observer>();
请注意,您正在通过迭代器foreach迭代
PauseScreen
观察者,在本例中,Start通过
initializeGame
方法中的可观察列表添加新玩家()
来修改基础列表:

public void initializeGame()  {
    EvoMain.player = new Player((float) 100, (float) 100);
    statScreen.add(EvoMain.player);
当在
PauseScreen.acknowledgeChanges()
foreach
循环中调用
iter.next
时,这将引发并发修改异常


解决这个问题很容易:从
可观察的
接口中删除
ArrayList observer
,并在实现类中创建(
PauseScreen
StatScreen

您需要发布异常堆栈跟踪。在这堵文本墙中猜测异常点既烦人又乏味,我将其进一步上移,现在应该在第四行。好的,让我看看~
StatScreen
constructor
this.add(btnClose)->这是超级
用户界面
类中的方法?是的。它将CloseButton添加到StatScreen的按钮列表中。我的按钮没有实现观察者界面。哇,非常感谢ạ新罕布什尔州奎伊ế唐古伊ễN我被困了两天,现在我终于可以继续编程了。非常感谢你!你的代码需要重构,但现在,让摇滚乐欢呼吧!
package userInterface;

import java.util.ArrayList;

import org.newdawn.slick.Color;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.SlickException;
import evo.Button;
import evo.EvoObject;

public abstract class UserInterface extends EvoObject {

boolean visible = false;
boolean enabled = false;
int width;
int height;
Color mainColor;
Color borderColor;
ArrayList<Button>   buttons = new ArrayList<Button>();

public UserInterface(float x, float y, int width, int height) {
    super(x, y);
    this.width = width;
    this.height = height;
}

public void render(GameContainer container, Graphics g) throws SlickException {
    if (visible) {  // render menu if visible
        g.setColor(mainColor);
        g.fillRect(getX(), getY(), this.width, this.height);
        g.setColor(borderColor);
        g.setLineWidth(3);
        g.drawRect(getX(), getY(), this.width, this.height);
        for (Button button : buttons) { // render all buttons
            button.render(container, g);
        }
    }
}

public boolean isVisible() {
    return visible;
}
public void setVisible(boolean visible) {
    this.visible = visible;
}
public boolean isEnabled() {
    return enabled;
}
public void setEnabled(boolean enabled) {
    this.enabled = enabled;
}
public int getWidth() {
    return width;
}
public void setWidth(int width) {
    this.width = width;
}
public int getHeight() {
    return height;
}
public void setHeight(int height) {
    this.height = height;
}
public Color getMainColor() {
    return mainColor;
}
public void setMainColor(Color mainColor) {
    this.mainColor = mainColor;
}
public Color getBorderColor() {
    return borderColor;
}
public void setBorderColor(Color borderColor) {
    this.borderColor = borderColor;
}
public void add(Button button) {
    buttons.add(button);
}
public ArrayList<Button> getButtons() {
    return buttons;
}
}
public interface Observable {

    ArrayList<Observer> observers = new ArrayList<Observer>();
case START:
     initializeGame();
     resumeGame();
public void initializeGame()  {
    EvoMain.player = new Player((float) 100, (float) 100);
    statScreen.add(EvoMain.player);