同步块 - 锁定多个对象

2022-08-31 23:54:43

我正在模拟一个游戏,其中多个玩家(线程)同时移动。玩家当前所在位置的信息存储两次:玩家有一个变量“hostField”,它引用棋盘上的一个字段,每个字段都有一个 ArrayList,存储当前位于该字段的玩家。

我对我有冗余信息的事实不是很满意,但我发现如果不循环使用大数据集,就无法避免这种情况。

但是,当玩家从一个字段移动到另一个字段时,我想确保(1)冗余信息保持链接(2)目前没有其他人正在操纵该字段。

因此,我需要做这样的事情

synchronized(player, field) {
    // code
}

这是不可能的,对吧?

我该怎么办?:)


答案 1

一个微不足道的解决方案是

synchronized(player) {
    synchronized(field) {
        // code
    }
}

但是,请确保始终以相同的顺序锁定资源,以避免死锁。

请注意,在实践中,瓶颈是场,因此场上的单个锁(或专用的,通用的锁对象,正如@ripper234正确地指出的那样)可能就足够了(除非您以其他冲突的方式同时操纵玩家)。


答案 2

实际上,同步是针对代码的,而不是对象或数据。在同步块中用作参数的对象引用表示锁。

因此,如果您有如下代码:

class Player {

  // Same instance shared for all players... Don't show how we get it now.
  // Use one dimensional board to simplify, doesn't matter here.
  private List<Player>[] fields = Board.getBoard(); 

  // Current position
  private int x; 

  public synchronized int getX() {
    return x;
  }

  public void setX(int x) {
    synchronized(this) { // Same as synchronized method
      fields[x].remove(this);
      this.x = x;
      field[y].add(this);
    }
  }
}

然后,尽管处于同步块中,但对字段的访问不受保护,因为锁定不相同(它在不同的实例上)。因此,您的棋盘的玩家列表可能会变得不一致,并导致运行时异常。

相反,如果您编写以下代码,它将起作用,因为我们只有一个适用于所有玩家的共享锁:

class Player {

  // Same instance shared for all players... Don't show how we get it now.
  // Use one dimensional board to simplify, doesn't matter here.
  private List<Player>[] fields; 

  // Current position
  private int x;

  private static Object sharedLock = new Object(); // Any object's instance can be used as a lock.

  public int getX() {
    synchronized(sharedLock) {
      return x;
    }
  }

  public void setX(int x) {
    synchronized(sharedLock) {
      // Because of using a single shared lock,
      // several players can't access fields at the same time
      // and so can't create inconsistencies on fields.
      fields[x].remove(this); 
      this.x = x;
      field[y].add(this);
    }
  }
}

确保仅使用单个锁来访问所有玩家,否则棋盘的状态将不一致。