简体   繁体   中英

Java multithreading: how to notify() a single thread after all threads are ready

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM