我正在尝试用 Java 实现一个文字游戏,每个玩家轮流从一组随机字母中提取一些字母,然后尝试用这些字母创建一个有效的单词。这就是我到目前为止所拥有的(为了清楚起见,进行了简化):
在 Game 类中,我通过为每个玩家运行一个线程(以及一个用于计时员)来开始游戏。我希望
activePlayers
列表中的第一个玩家(最初与 players
列表相同)迈出第一步,因此我初始化了 turn
和 turnIndex
属性以对应于该玩家:
public void play()
{
this.turn = activePlayers.get(0); //the player who joined first goes first
this.turnIndex = 0; //the player's index in the ArrayList
for(Player player : players) {
new Thread(player).start();
}
new Thread(new Timekeeper()).start(); //keeps track of the game's duration
}
在 Player 类中,我希望待命的玩家不做任何事情,只是等待当前玩家完成他们的任务,因此是第一个 while 循环。然后,当一个玩家的回合结束时,我希望该线程将监视器交给另一个玩家的线程并等待其下一个回合。这就是我决定采取的方法:
private synchronized boolean submitWord() throws InterruptedException
{
while(game.turn != this)
{
System.out.println(this.name + " is waiting their turn...");
wait();
}
Thread.sleep(1000);
List<Tile> extracted = game.getBag().extractTiles(wordLength);
if(extracted.isEmpty())
return false; //if there are no more letters to extract, the thread ends its execution
//game logic goes here - creating and validating the word
//after this player is done, the next player makes their move
game.turnIndex++;
if(game.turnIndex >= game.activePlayers.size())
game.turnIndex = 0;
game.turn = game.activePlayers.get(game.turnIndex);
notifyAll();
return true;
}
@Override
public void run()
{
do {
try {
this.running = this.submitWord();
} catch(InterruptedException e) {
System.out.println("Something went wrong with " + this.name + "...");
e.printStackTrace();
}
} while(this.running);
game.activePlayers.remove(this); //the player is now inactive
if(game.winner == this)
System.out.println("Winner: " + this.name + " [" + this.score + " points]");
}
但是,当我尝试运行该程序时,我得到这样的信息:
Player 2 is waiting their turn...
Player 3 is waiting their turn...
1 seconds elapsed...
Player 1: AERIAL [36 points]
Player 1 is waiting their turn...
2 seconds elapsed...
3 seconds elapsed...
4 seconds elapsed...
5 seconds elapsed...
6 seconds elapsed...
基本上,游戏不会超过玩家 1 的第一次尝试,我会陷入无限循环,什么也不会发生。我是否没有正确使用 wait() 和 notifyAll() 方法?我应该如何让玩家线程相互通信?
如果我正确理解了您的代码,则该
submitWord()
方法属于 Player
类。在多线程开发中,应该使用synchronized
关键字来获取共享对象的监视器,以限制不同线程同时访问同一资源,避免竞争情况。
就您而言,您正在通过
Player
线程进行同步,这不是正确的设计。您应该通过共享资源(即游戏对象)进行同步。此外,尝试使用同步块而不是整个同步方法,因为后者更有可能阻塞其他线程。
在
Player
的run
方法中,您应该首先检查线程是否可以通过同步块获取游戏的监视器。如果可以,那么您可以通过将游戏对象的 turnIndex
与 Player
的索引相对来检查是否轮到玩家了。如果不是轮到玩家,那么线程应该调用游戏对象上的 wait()
方法;否则它应该通过调用 submitWord()
来继续。
在您的代码中,您忘记在
notify()
方法中的 notifyAll()
之前添加 return false
(或 submitWord()
)。这种失误可能会导致线程卡住的情况。这是代码的调整版本:
//This method can be called only under the condition the the game's monitor has already been acquired. So, it can only be invoked within a synchronized block.
private boolean submitWord() {
List<Tile> extracted = game.getBag().extractTiles(wordLength);
if(extracted.isEmpty()){
//notify is more efficient than notifyAll, as it causes less overhead by awakening only one random thread instead of all the ones waiting
this.game.notify();
//you forgot to call a notify() before returning.
//this might have caused your threads to get stuck
return false;
}
//game logic goes here - creating and validating the word
//Rotating the turn
game.turnIndex = (this.game.turnIndex + 1) % this.game.activePlayers.size();
game.turn = game.activePlayers.get(game.turnIndex);
this.game.notify();
return true;
}
@Override
public void run() {
do {
synchronized(this.game){
if (this.game.indexTurn == this.index){
this.running = this.submitWord();
//It's better to check here whether the player must be removed or not,
//as you already own the game's lock
if (!this.running){
this.game.activePlayers.remove(this);
}
} else {
try {
this.game.wait();
} catch(InterruptedException e) {
System.out.println("Something went wrong with " + this.name + "...");
e.printStackTrace();
}
}
}
} while(this.running);
//you should re-acquire the game's lock here since you're modifying the set of players
//synchronized(this.game){
// this.game.activePlayers.remove(this);
//}
if(this.game.winner == this){
System.out.println("Winner: " + this.name + " [" + this.score + " points]");
}
}
只是一个想法,但睡眠可能发生在同步方法内:Thread.sleep 也阻塞其他线程,在其他方法上工作,以及在同步方法内调用自身