複数のプレイヤー(スレッド)が同時に移動するゲームをモデリングしています。プレーヤーの現在の位置に関する情報は2回保存されます。プレーヤーには、ボード上のフィールドを参照する変数「hostField」があり、すべてのフィールドには、現在このフィールドにいるプレーヤーを格納するArrayListがあります。
冗長な情報があるという事実にはあまり満足していませんが、大きなデータセットをループせずにそれを回避する方法は見つかりませんでした。
ただし、あるフィールドから別のフィールドにプレイヤーが移動する場合、(1)冗長な情報がリンクされたままである(2)現時点で他の誰もフィールドを操作していないことを確認したいと思います。
したがって、私は次のようなことをする必要があります
synchronized(player, field) {
// code
}
どちらが不可能ですか?
私は何をすべきか? :)
実際、同期はオブジェクトやデータではなくコード用です。同期ブロックでパラメーターとして使用されるオブジェクト参照は、ロックを表します。
したがって、次のようなコードがある場合:
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);
}
}
}
その後、同期ブロックにあるにもかかわらず、ロックが同じではないため(フィールドが異なる場合)、フィールドへのアクセスは保護されません。そのため、ボードのプレーヤーのリストが不整合になり、ランタイム例外が発生する可能性があります。
代わりに、次のコードを記述すると、すべてのプレーヤーに対して共有ロックが1つしかないため、機能します。
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);
}
}
}
すべてのプレイヤーにアクセスするには、必ず単一のロックのみを使用してください。そうしないと、ボードの状態に一貫性がなくなります。
些細な解決策は
synchronized(player) {
synchronized(field) {
// code
}
}
ただし、デッドロックを回避するために、必ず同じ順序でリソースをロックしてくださいalways。
実際には、ボトルネックはフィールドであるため、フィールド上の単一のロック(または@ ripper234が正しく指摘されているように、専用の共通ロックオブジェクト)で十分な場合があることに注意してください(プレイヤーが他の競合する方法で同時に操作している場合を除きます) 。
並行性を使用する場合、適切な応答を提供することは常に困難です。それは、あなたが実際に何をしていて、何が本当に重要であるかに大きく依存しています。
私の理解では、プレーヤーの動きには以下が含まれます。
1プレーヤー位置の更新。
2前のフィールドからプレーヤーを削除します。
3新しいフィールドにプレーヤーを追加します。
同時に複数のロックを使用するが、一度に1つのロックのみを取得することを想像してください。一部のプレーヤーは、例のためにボードから姿を消したように見えることがあります。
このようにロックがロックされていると想像してください:
synchronized(player) {
synchronized(previousField) {
synchronized(nextField) {
...
}
}
}
問題は...動作しない、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 :
スレッド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.
=>両方のプレイヤーがブロックされています。
私の意見では、ゲームの状態全体に対して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」が保護されていれば問題ありません。
「移動」セマンティクスのためにさらに原子性が必要な場合は、ボードのレベルを1つ上げます。
すべての答えを読んで、次のデザインを適用しようとしました。
1.デッドロックを回避し、3。プレイヤーが待機している間に状況が変化する可能性があるため重要です。
さらに、ゲームでは複数のプレイヤーがフィールドに留まることができるので、フィールドをロックせずに進むことができます。特定のスレッドについてのみ相互作用を行う必要があります。この相互作用は、プレーヤーを同期することで実行できます-フィールドを同期する必要はありません...
どう思いますか?