synchronized块 - 锁定多个对象

时间:2011-01-05 12:32:14

标签: java multithreading locking synchronized

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

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

然而,当玩家从一个领域移动到另一个领域时,我想确保(1)冗余信息保持联系(2)此刻没有其他人在操纵该领域。

因此我需要做一些像

这样的事情
synchronized(player, field) {
    // code
}

哪种情况不可能,对吧?

我该怎么办? :)

5 个答案:

答案 0 :(得分:53)

一个简单的解决方案是

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

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

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

答案 1 :(得分:21)

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

所以如果您有以下代码:

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

请务必仅使用一个锁来访问所有玩家,否则您的棋盘状态将会不一致。

答案 2 :(得分:7)

使用并发时,总是很难给出好的回复。这在很大程度上取决于你的确在做什么以及真正重要的事情。

根据我的理解,球员移动涉及:

1个更新玩家位置。

2从前一个字段中删除播放器。

3将玩家添加到新领域。

想象一下,您同时使用多个锁,但一次只能获取一个: - 另一个玩家可以完美地看到错误的时刻,基本上在1&amp; 2或2&amp; 3之间。例如,有些玩家似乎已经从董事会中消失了。

想象一下,你这样做是一种错误的锁定:

synchronized(player) {
  synchronized(previousField) {
    synchronized(nextField) {
      ...
    }
  }
}

问题是......它不起作用,请看两个线程的执行顺序:

Thread1 :
Lock player1
Lock previousField
Thread2 :
Lock nextField and see that player1 is not in nextField.
Try to lock previousField and so way for Thread1 to release it.
Thread1 :
Lock nextField
Remove player1 from previous field and add it to next field.
Release all locks
Thread 2 : 
Aquire Lock on previous field and read it : 

线程2认为player1与整个主板一样消失。如果这对您的应用程序来说是个问题,则无法使用此解决方案。

imbriqued锁定的附加问题:线程可能会卡住。 想象一下2名球员:他们在同一时间交换位置:

player1 aquire it's own position at the same time
player2 aquire it's own position at the same time
player1 try to acquire player2 position : wait for lock on player2 position.
player2 try to acquire player1 position : wait for lock on player1 position.

=&GT;两名球员都被封锁了。

在我看来,最佳解决方案是只使用一个锁,用于整个游戏状态。

当玩家想要阅读状态时,它会锁定整个游戏状态(玩家和玩家),并为自己的使用制作副本。然后它可以在没有任何锁定的情况下处理。

当玩家想要写入状态时,它会锁定整个游戏状态,写入新状态然后释放锁定。

=&GT;锁定仅限于游戏状态的读/写操作。玩家可以在自己的副本上对董事会状态进行“长期”检查。

这可以防止任何不一致的状态,比如几个领域的玩家或者没有,但是不要阻止玩家使用“旧”状态。

它可能看起来很奇怪,但它是象棋游戏的典型案例。当您等待其他玩家移动时,您会看到移动前的棋盘。你不知道其他玩家会做出什么动作,直到他最终移动,你才会处于“旧”状态。

答案 3 :(得分:1)

你不应该对你的建模感到沮丧 - 这只是一种双向导航关联。

如果你注意(如在其他答案中所说)操纵原子,例如在Field方法中,没关系。


public class Field {

  private Object lock = new Object();

  public removePlayer(Player p) {
    synchronized ( lock) {
      players.remove(p);
      p.setField(null);
    }
  }

  public addPlayer(Player p) {
    synchronized ( lock) {
      players.add(p);
      p.setField(this);
    }
  }
}


如果“Player.setField”受到保护,那就没问题了。

如果你需要进一步使用“移动”语义的原子性,那就去上一层了。

答案 4 :(得分:0)

阅读所有答案,我尝试应用以下设计:

  1. 只锁定玩家,而不是字段
  2. 仅在同步方法/块中进行字段操作
  3. 在synchronized方法/块中始终首先检查导致同步方法/块被调用的前提条件是否仍然如此
  4. 我认为1.避免死锁和3.重要的是因为玩家等待时事情会发生变化。

    此外,我可以不使用锁定字段,因为在我的游戏中,不止一个玩家可以留在一个字段中,只有某些线程才能进行交互。这种互动可以通过同步玩家来完成 - 无需同步字段......

    您怎么看?