多人2D游戏中的数据结构(Java)

多人2D游戏中的数据结构(Java),java,networking,arraylist,multiplayer,Java,Networking,Arraylist,Multiplayer,我有以下问题: 我已经编写了一个具有多人游戏功能的2D游戏。现在,我将其他玩家数据和游戏对象存储在两个ArrayList中(世界以其他方式存储)。有时网络线程发送更新,但无法应用,因为游戏会绘制玩家/游戏对象(java.util.ConcurrentModificationException)。由于该绘图过程每秒发生约60次(由于动画),因此问题经常出现(每2秒一次)。这是players ArrayList的代码: 吸引玩家: for (Player p : oPlayer) { if (p

我有以下问题:

我已经编写了一个具有多人游戏功能的2D游戏。现在,我将其他玩家数据和游戏对象存储在两个ArrayList中(世界以其他方式存储)。有时网络线程发送更新,但无法应用,因为游戏会绘制玩家/游戏对象(java.util.ConcurrentModificationException)。由于该绘图过程每秒发生约60次(由于动画),因此问题经常出现(每2秒一次)。这是players ArrayList的代码:

吸引玩家:

for (Player p : oPlayer) {
  if (p != null) {
    int x = (int) ((width / 2) + (p.x - getPlayerX()) * BLOCK_SIZE);
    int y = (int) ((height / 2) + (p.y - getPlayerY()) * BLOCK_SIZE);
    g.drawImage(onlinePlayer, x, y, BLOCK_SIZE, BLOCK_SIZE, null);
    FontMetrics fm = g.getFontMetrics();
    g.setColor(Color.DARK_GRAY);
    g.drawString(p.getName(), x + (BLOCK_SIZE / 2) - (fm.stringWidth(p.getName()) / 2), y - 5);
  }
}
case "ADP": //add Player
  Game.oPlayer.add(new Player(message, id));
  sendX();
  sendY();
  break;
case "SPX": // set X
  for (Player p : Game.oPlayer) {
    if (p.getId() == id) {
      p.setX(Short.parseShort(message));
      break;
    }
  }
  break;
case "SPY": // set Y
  for (Player p : Game.oPlayer) {
    if (p.getId() == id) {
      p.setY(Short.parseShort(message));
      break;
    }
  }
  break;
case "PDI": // remove Player
  for (Player p : Game.oPlayer) {
    if (p.getId() == id) {
      Game.oPlayer.remove(p);
      break;
    }
  }
  break;
编辑网络线程中的信息:

for (Player p : oPlayer) {
  if (p != null) {
    int x = (int) ((width / 2) + (p.x - getPlayerX()) * BLOCK_SIZE);
    int y = (int) ((height / 2) + (p.y - getPlayerY()) * BLOCK_SIZE);
    g.drawImage(onlinePlayer, x, y, BLOCK_SIZE, BLOCK_SIZE, null);
    FontMetrics fm = g.getFontMetrics();
    g.setColor(Color.DARK_GRAY);
    g.drawString(p.getName(), x + (BLOCK_SIZE / 2) - (fm.stringWidth(p.getName()) / 2), y - 5);
  }
}
case "ADP": //add Player
  Game.oPlayer.add(new Player(message, id));
  sendX();
  sendY();
  break;
case "SPX": // set X
  for (Player p : Game.oPlayer) {
    if (p.getId() == id) {
      p.setX(Short.parseShort(message));
      break;
    }
  }
  break;
case "SPY": // set Y
  for (Player p : Game.oPlayer) {
    if (p.getId() == id) {
      p.setY(Short.parseShort(message));
      break;
    }
  }
  break;
case "PDI": // remove Player
  for (Player p : Game.oPlayer) {
    if (p.getId() == id) {
      Game.oPlayer.remove(p);
      break;
    }
  }
  break;

提前感谢:)

如果在一个线程和另一个线程中迭代或修改列表,您将得到一个
ConcurrentModificationException
。在一般情况下,用户界面应用程序将修改模型数据限制为单个线程,通常是用户界面线程,如Swing的事件调度线程或JavaFX中的平台线程


另一方面,对于JavaFX,存在一种为游戏开发提供现成技术的方法。JavaFX通常比AWT或Swing更适合图形密集型工作

您尝试过使用Vector吗?它是集合的一部分并且是同步的。

这里发生的是,两个线程在同一个列表上工作。
第一个是读取列表(
for(Player p:oPlayer){
),第二个是修改列表(
Game.oPlayer.add(newplayer(message,id));
)。这会使oPlayer列表变得(有点“不一致”Java看到您修改了正在阅读的内容,并抛出此异常,以让您知道某些内容不符合犹太教。
可以找到有关ConcurrentModificationException的更多信息

为了澄清这一点,您使用了所谓的。您有一个读取Game.oPlayer数据的读卡器(线程)和一个将数据写入Game.oPlayer的写卡器(线程)

解决
同步关键字 下面解释了
synchronized
关键字。您可以这样使用它:

private final List<Player> players = ...;

public void addPlayer(Player player) {
    synchronized(players) {
        players.add(player);
    }
}

public void removePlayer(Player player) {
    synchronized(players) {
        players.remove(player);
    }
}
try {
lock.lock();
// work ..
} finally {
lock.unlock();
}
List<Player> toIterate;
synchronized(players) {
    toIterate = new ArrayList<>(getPlayerList());
}
for(Player player : toIterate) {
    // work
}
synchronized(players) {
    for(Player player : players) {
        // work
    }
}
因此,只有一个线程可以通过说出
lock.lock()
来访问工作部件。如果任何其他线程使用lock.lock()锁定了工作部件,但未将其解锁,则当前线程将等待到
lock.unlock()
被调用。使用try finall块来确保锁已解锁,即使您的工作部件正在抛出一个可丢弃的锁


此外,我建议您在球员名单的“副本”上进行评级,如下所示:

private final List<Player> players = ...;

public void addPlayer(Player player) {
    synchronized(players) {
        players.add(player);
    }
}

public void removePlayer(Player player) {
    synchronized(players) {
        players.remove(player);
    }
}
try {
lock.lock();
// work ..
} finally {
lock.unlock();
}
List<Player> toIterate;
synchronized(players) {
    toIterate = new ArrayList<>(getPlayerList());
}
for(Player player : toIterate) {
    // work
}
synchronized(players) {
    for(Player player : players) {
        // work
    }
}
第一个实例为您提供该实例的副本,这基本上意味着,它包含与原始列表相同的对象,但它不是同一个列表。它帮助您让更多线程在自己的“列表”上工作并完成它们的作业,而不管当前是否有更新,因为第二个示例将在以下情况下阻止:

  • 任何线程都想读取列表
  • 任何线程都会修改列表
  • 因此,在第一个示例中,您只需同步coppy部分


    即使是furhter(不是你问题的一部分,但仍然可以让它变得更简单),我也建议不要使用静态,正如你在
    Game.oPlayer.[…]
    中所说的,并查看一下

    您可以修改游戏类,以提供方法
    addPlayer(Player Player);
    removePlayer(Player Player);
    getPlayerList();
    以某种方式实现代码。

    通过这种设计,您可以轻松地修改代码,以处理新的并发问题。

    这应该是一个注释而不是答案。这就是问题的答案,为什么会是注释?这是一个解决方案,而不是问题的答案。