【问题标题】:Data Structures in 2D-Games with Multiplayer (Java)多人 2D 游戏中的数据结构 (Java)
【发布时间】:2017-10-06 18:38:27
【问题描述】:

我有以下问题:

我编写了一个具有多人游戏功能的 2D 游戏。现在我将其他玩家数据和游戏对象存储在两个 ArrayList 中(否则将存储世界)。有时网络线程发送无法应用的更新,因为游戏绘制了玩家/游戏对象 (java.util.ConcurrentModificationException)。因为这个绘图过程每秒发生大约 60 次(因为动画),所以问题经常出现(每 2 秒)。这是玩家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);
  }
}

在 Network-Thread 中编辑信息:

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;

提前谢谢你:)

【问题讨论】:

    标签: java networking arraylist multiplayer


    【解决方案1】:

    这里发生的是,2 个线程在同一个列表上工作。
    第一个是读取列表(for (Player p : oPlayer) {),第二个是修改它(Game.oPlayer.add(new Player(message, id));)。这会使 oPlayer 列表进入(某种)“不一致”状态。 Java 发现您修改了正在阅读的内容并抛出此异常以告知您某些内容不符合犹太教规。
    有关 ConcurrentModificationExceptions 的更多信息可以找到here

    为了澄清,您使用了所谓的Readers-writer problem。您有一个读取器(线程),它读取 Game.oPlayer 的数据和一个写入器(线程),将数据写入 Game.oPlayer。

    解决方案


    同步关键字

    synchronized 关键字解释为here。你可以这样使用它:

    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);
        }
    }
    

    请注意,列表必须是最终的。此外,我使用的是本地属性而不是静态属性。删除 playersGame.oPlayer 以获得合适的解决方案。
    这仅允许 1 个线程访问 players.add()players.remove()


    锁定

    关于如何使用锁的信息可以在here找到。

    说得简单,你可以这样创建一个块:

    try {
    lock.lock();
    // work ..
    } finally {
    lock.unlock();
    }
    

    这样只有一个线程可以通过lock.lock() 访问工作部分。如果任何其他线程使用 lock.lock() 锁定了工作部分并且没有解锁它,则当前线程将等待直到调用lock.unlock()。使用 try-finall 块来确保锁已解锁,即使您的工作部分正在投掷可投掷物。


    此外,我建议像这样遍历播放器列表的“副本”:

    List<Player> toIterate;
    synchronized(players) {
        toIterate = new ArrayList<>(getPlayerList());
    }
    for(Player player : toIterate) {
        // work
    }
    

    或者像这样完全同步这部分:

    synchronized(players) {
        for(Player player : players) {
            // work
        }
    }
    

    第一个为您提供了该实例的副本,这基本上意味着,它包含与原始列表相同的对象,但它不是相同的列表。它可以通过让更多线程在自己的“列表”上工作并完成他们的工作来帮助您,而不管当前是否有更新,因为第二个示例将在以下情况下阻塞:

    1. 任何线程都想读取列表。
    2. 任何线程都会修改列表。

    所以你只需要在第一个例子中同步 coppy 部分。


    更进一步(不是你的问题的一部分,但仍然可以让它更容易)我建议不要使用静态,正如你在Game.oPlayer.[...] 中所说的那样,看看Dependency Injection

    您可以修改您的 Game 类以提供 addPlayer(Player player);removePlayer(Player player);getPlayerList(); 方法以真正以 Object Oriented 方式编写代码。
    通过这种设计,您可以轻松修改代码,以处理新的并发问题。

    【讨论】:

      【解决方案2】:

      如果一个列表在一个线程和另一个线程中迭代或修改,您将获得ConcurrentModficationException。一般来说,用户界面应用程序将模型数据的修改限制在单个线程中,通常是用户界面线程,例如 Swing 的事件分发线程,或者 JavaFX 中的平台线程。

      顺便说一句,对于 JavaFX,有一个 game library 为游戏开发提供开箱即用的技术。 JavaFX 通常比 AWT 或 Swing 更适合图形密集型工作。

      【讨论】:

        【解决方案3】:

        您是否尝试过使用 Vector 代替?它是 Collection 的一部分并且是同步的。

        【讨论】:

        • 这应该是评论而不是答案。
        • 这就是问题的答案,为什么会是评论?
        • 这是一个解决方案,而不是问题的答案。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-07-24
        • 2014-01-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-10-18
        相关资源
        最近更新 更多