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正确指出)可能就足够了(除非您以其他相互冲突的方式同时操纵玩家) .
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)
确保只使用一个锁来访问所有玩家,否则你的棋盘状态就会不一致.
使用并发时,总是很难给出好的响应.这在很大程度上取决于你的确在做什么和真正重要的事情.
根据我的理解,玩家移动涉及:
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)
=>两名球员都被封锁了.
在我看来,最好的解决方案是只使用一个锁,用于整个游戏状态.
当玩家想要阅读状态时,它会锁定整个游戏状态(玩家和棋盘),并为自己的使用制作副本.然后可以在没有任何锁定的情况下进
当玩家想要写状态时,它会锁定整个游戏状态,写入新状态然后释放锁定.
=>锁定仅限于游戏状态的读/写操作.玩家可以在自己的副本上对董事会状态进行"长期"检查.
这可以防止任何不一致的状态,比如几个领域的玩家或者没有,但是不要阻止该玩家使用"旧"状态.
它可能看起来很奇怪,但它是象棋游戏的典型案例.当您等待其他玩家移动时,您会看到移动前的棋盘.你不知道其他玩家会做出什么动作,直到他最终移动,你就会处于"旧"状态.
归档时间: |
|
查看次数: |
43352 次 |
最近记录: |