当使用并发时,很难给出良好的响应。这在很大程度上取决于你正在做什么以及什么真正重要。
据我了解,玩家移动涉及以下内容:
1. 更新玩家位置。
2. 从先前的区域中删除玩家。
3. 将玩家添加到新区域。
想象一下,您同时使用几个锁,但每次只获取一个:
- 另一个玩家可以在错误的时刻看到,基本上是在1和2之间或2和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认为玩家1已从整个棋盘消失。 如果这对您的应用程序有问题,您将无法使用此解决方案。
嵌套锁定的附加问题:线程可能会卡住。
想象一下两个玩家:他们在完全相同的时间交换位置:
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.
=> 两个玩家都被阻塞了。
我认为最好的解决方案是只使用一个锁来保护整个游戏状态。
当一个玩家想要读取状态时,它会锁定整个游戏状态(包括玩家和棋盘),并为自己的使用创建一个副本。然后它可以在没有任何锁的情况下处理。
当一个玩家想要写入状态时,它会锁定整个游戏状态,写入新状态,然后释放锁定。
=> 锁定仅限于游戏状态的读/写操作。玩家可以对自己的副本进行“长时间”的棋盘状态检查。
这可以防止任何不一致的状态,例如一个玩家在几个领域中或没有在任何领域中,但不能防止该玩家使用“旧”状态。
这可能看起来很奇怪,但这是象棋游戏的典型情况。当你等待另一个玩家移动时,你看到的是移动之前的棋盘状态。你不知道其他玩家会做出什么移动,在他最终移动之前,你都是在处理一个“旧”状态。