I'm writing a game where a Player
can be either local or remote, so I have to read the "move" made by him using multithreading.
I have a PlayerLoop
thread which initially calls start()
on several Player
threads. After that, PlayerLoop
should repeatedly wake up a single Player
(going from the first to the last one) in a cyclic manner, until the game ends.
However, not always the Player
will wake up: sometimes PlayerLoop
notifies the correct Player
, but it does that too early (the Player
isn't yet "waiting his turn").
Specifically, when I start the PlayerLoop
for the first time it seems to work OK, but if during the game I cancel the match and start a new one, then every Player
just waits their turn forever.
Here's the PlayerLoop
:
public class PlayerLoop extends Thread {
@Override
public void run() {
System.out.println("Thread [" + Thread.currentThread().getName()
+ "] is alive.");
this.startPlayers(); // cycles Players and calls .start()
while (true) {
Player current = this.getCurrentPlayer();
current.connect(); // connect to this Player
synchronized (current) {
try {
System.out.println("Notifying Thread [" + current.getName()
+ "] that it's his turn.");
current.notify(); // the player thread "resumes" to eventually produce a "move"
current.wait(); // wait for the "move" to be ready
} catch (InterruptedException e) {
// The game loop is interrupted, terminate thread
break;
}
}
this.applyMove(current.getMove()); // use the "move"
current.disconnect(); // disconnect this Player
this.goToNextPlayer();
}
System.out.println("Terminating players...");
this.stopPlayers(); // cycles Players and calls .interrupt()
System.out.println("Thread [" + Thread.currentThread().getName()
+ "] is dead.");
}
For the moment I'm not testing the "remote" part so here's just the LocalPlayer
class, which extends Player
, which extends Thread
:
public class LocalPlayer extends Player {
@Override
public void run() {
System.out.println("Thread [" + Thread.currentThread().getName()
+ "] is alive.");
while (true) {
// Wait my turn
synchronized (this) {
try {
System.out.println("Thread ["
+ Thread.currentThread().getName()
+ "] is waiting its turn.");
this.wait();
} catch (InterruptedException e) {
// We are stopping all the players, so terminate thread
break;
}
System.out.println("Thread ["
+ Thread.currentThread().getName()
+ "] has come to its turn.");
}
// Wait for mainLoop to produce a Move
synchronized (__SomeGameLoop__) {
System.out.println("Thread ["
+ Thread.currentThread().getName()
+ "] is waiting a move.");
try {
__SomeGameLoop__.wait();
} catch (InterruptedException e) {
// PlayerLoop was interrupted, and he is now interrupting me, so terminate thread
break;
}
System.out.println("Thread ["
+ Thread.currentThread().getName()
+ "] has been notified of a move.");
}
// Get produced move
this.move = __SomeGameLoop__.getMove();
// Signal monitor
synchronized (this) {
// Notify that I have read the move and that I can communicate it
this.notify();
}
}
System.out.println("Thread [" + Thread.currentThread().getName()
+ "] is dead.");
}
}
The code above is probably not precise and oversimplified, but here's an excerpt of the game's output:
Creating threads...
Thread [PlayerLoop] is alive.
Thread [Player 1] is alive.
Thread [Player 2] is alive.
Thread [Player 2] is waiting its turn.
Thread [Player 1] is waiting its turn.
Notifying Thread [Player 1] that it's his turn.
Thread [Player 1] has come to its turn.
Thread [Player 1] is waiting a move.
GameLoop notifies that a move has been done.
Thread [Player 1] has been notified of a move.
Notify that I have read that move and I can communicate it
Thread [Player 1] is waiting its turn.
Notifying Thread [Player 2] that it's his turn.
Thread [Player 2] has come to its turn.
Thread [Player 2] is waiting a move.
GameLoop notifies that a move has been done.
Thread [Player 2] has been notified of a move.
Notify that I have read that move and I can communicate it
Thread [Player 2] is waiting its turn.
Notifying Thread [Player 1] that it's his turn.
Thread [Player 1] has come to its turn.
Thread [Player 1] is waiting a move.
GameLoop notifies that a move has been done.
Thread [Player 1] has been notified of a move.
Notify that I have read that move and I can communicate it
Thread [Player 1] is waiting its turn.
Notifying Thread [Player 2] that it's his turn.
Thread [Player 2] has come to its turn.
Thread [Player 2] is waiting a move.
Terminating players...
Thread [PlayerLoop] is dead.
PlayerLoop was interrupted, and he is now interrupting me, so terminate thread [Player 2]
We are stopping all the players, so terminate thread [Player 1]
Thread [Player 1] is dead.
Thread [Player 2] is dead.
Creating threads...
Thread [PlayerLoop] is alive.
Thread [Player 1] is alive.
Notifying Thread [Player 1] that it's his turn.
Thread [Player 1] is waiting its turn.
Thread [Player 2] is alive.
Thread [Player 2] is waiting its turn.
....(and here it hangs indefinitely)....
The second time threads are created, PlayerLoop
notifies the Player
too early. How can I make sure to notify()
a Player
only after all the Player
s are wait()
ing on their respective monitor?
A player should not wait their turn unless it is not yet their turn. Waiting for a condition is unconditional . So if you don't need to wait for a condition, don't wait for it. The reason the method that calls wait
is synchronized is so that you can make absolutely sure you need to wait before you wait.
If there is something to wait for, waiting is always safe. If there is nothing to wait for, there is no reason to wait. Do not wait for something that has already happened, it won't happen.
Please try Semaphore from Java Standard API, it 's easy and less confusing.
Object.wait and Object.notify is too low level to use rightly.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.