synchronized块 - 锁定多个对象

spe*_*ndo 38 java multithreading locking synchronized

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

对于我有冗余信息这一事实,我不是很满意,但是如果没有循环遍历大数据集,我发现没有办法避免这种情况.

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

所以我需要做点什么

synchronized(player, field) {
    // code
}
Run Code Online (Sandbox Code Playgroud)

哪个不可能,对吧?

我该怎么办?:)

Pét*_*rök 54

一个微不足道的解决方案

synchronized(player) {
    synchronized(field) {
        // code
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

  • @ ripper234,哦,那些同事 - 总是困扰着琐事并抢夺我们宝贵的时间,否则我们可以和平地花在SO上;-) (2认同)

Nic*_*uet 22

实际上,同步是针对代码而不是对象或数据.用作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);
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

然后,尽管在同步块中,对字段的访问不受保护,因为锁不相同(它在不同的实例上).因此,您的电路板的播放器列表可能会变得不一致并导致运行时异常.

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

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);
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

确保只使用一个锁来访问所有玩家,否则你的棋盘状态就会不一致.


Nic*_*uet 7

使用并发时,总是很难给出好的响应.这在很大程度上取决于你的确在做什么和真正重要的事情.

根据我的理解,玩家移动涉及:

1更新球员位置.

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

3将玩家添加到新领域.

想象一下,你同时使用几个锁,但一次只能获得一个锁: - 另一个玩家可以完美地看错时刻,基本上在1和2或2和3之间.有些玩家似乎已经从董事会中消失了.

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

synchronized(player) {
  synchronized(previousField) {
    synchronized(nextField) {
      ...
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

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

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 : 
Run Code Online (Sandbox Code Playgroud)

线程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.
Run Code Online (Sandbox Code Playgroud)

=>两名球员都被封锁了.

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

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

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

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

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

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