为什么我的等待线程即使收到通知也没有醒来?

Why is my waiting thread not waking up even though it's being notified?

我有一个客户端服务器 tic-tac-toe 游戏,它试图 运行 为每个玩家创建一个不同的线程(在不同的终端中),我在 eclipse 中构建了它。

我的目标是让每个玩家移动,.notify() 另一个玩家,然后 .wait() 让另一个玩家移动,并交替执行该过程,直到游戏结束。

toSync 是用于同步的对象

public static final Object toSync = new Object()

并且在播放器 class 中找到(由 XPlayer 和 OPlayer 扩展)。

似乎导致问题的行在 XPlayer 和 Oplayer 中有注释:

Xplayer和OPlayer都有main方法,可以同时运行。 X 迈出第一步,然后使用套接字将此移动传递给服务器。
服务器将此着法传递给 O,然后 O 做出自己的着法并将其传回给服务器。这种情况交替进行,直到游戏结束。

作为 x 玩家迈出第一步工作正常,但一旦迈出第一步,o 应该显示棋盘,然后提示用户他们的移动。然而,这并没有发生:x 开始行动,并且 o 理应收到通知,但实际上从未醒来。永远不会到达 OPlayer 中注释的结束 while 循环的大括号(通过到目前为止我所做的调试,我知道这是真的)。​​

Class XPlayer:

import java.io.*;

public class XPlayer 
extends Player
implements Runnable
{
    public static volatile boolean xTurn = true;

    public XPlayer() throws IOException
    {
        super();
        mark = LETTER_X;
    }

    public void run()
    {
        try
        {
            System.out.println("Okay " + name + ", You will be the X-Player");

            synchronized(toSync)
            {
                Cell move = makeMove();
                out.println(move.toString());
                board.addMark
                    (move.row(),move.col(),move.mark());
                board.display();

                xTurn = false;
                toSync.notifyAll();    //THIS IS THE LINE THAT ISNT WORKING!!

                System.out.println(WAITING);
            }
            synchronized(toSync)
            {
                while (!xTurn)
                    {toSync.wait();}
            }

            while (!board.isOver())
            {
                synchronized(toSync)
                {
                    String line;
                    do {line = in.readLine();}
                    while (line == null);   

                    Cell opponentMove = Cell.split(line);
                    board.addMark
                        (opponentMove.row(),opponentMove.col(), opponentMove.mark());

                    String move = makeMove().toString();
                    out.println(move);
                    xTurn = false;
                    toSync.notifyAll();

                    while (!xTurn)
                        {toSync.wait();}
                }
            }

            endGame();

            sock.close();
            in.close();
            stdin.close();
            out.close();

        } catch (InterruptedException ie)
        {
            System.out.println("IE IN XPLAYER! " + ie.getMessage());
            System.exit(1);

        } catch (IOException ioe) 
        {
            System.out.println("IOE IN XPLAYER! " + ioe.getMessage());
            System.exit(1);
        }
    }
    public static void main(String[] args)
    {
        try
        {
            XPlayer x = new XPlayer();
            Thread t = new Thread(x);
            t.start();

        } catch(IOException ioe)
        {
            System.err.println
                ("IOE IN XPLAYER MAIN " + ioe.getMessage());
            System.exit(1);
        }
    }

Class OPlayer:

import java.io.*;

public class OPlayer 
extends Player
implements Runnable
{

    public OPlayer() throws IOException
    {
        super();
        mark = LETTER_O;
    }

    public void run() 
    {
        try
        {       
            synchronized(toSync)
            {
                System.out.println("Okay " + name + ", You will be the O-Player");

                System.out.println(WAITING);
                while(!XPlayer.xTurn)
                    {toSync.wait();}    // THIS IS THE LINE THAT ISN'T WAKING UP

                while (!board.isOver())
                {
                    String line;

                    do {line = in.readLine();}
                    while (line == null);   

                    Cell opponentMove = Cell.split(line);
                    board.addMark
                        (opponentMove.row(),opponentMove.col(),opponentMove.mark());

                    Cell move = makeMove();
                    out.println(move.toString());
                    board.addMark(move.row(),move.col(),move.mark());
                    board.display();
                    XPlayer.xTurn = true;
                    toSync.notifyAll();

                    System.out.println(WAITING);
                    while (XPlayer.xTurn)
                        {toSync.wait();}    
                }
            }

            endGame();

            sock.close();
            in.close();
            stdin.close();
            out.close();

        } catch (InterruptedException ie)
        {
            System.out.println("IE IN OPLAYER " + ie.getMessage());
            System.exit(1);

        } catch (IOException ioe)
        {
            System.err.println("IOE IN OPLAYER " + ioe.getMessage());
            System.exit(1);
        }
    }

    public static void main(String[] args)
    {
        try
        {
            OPlayer o = new OPlayer();
            Thread t = new Thread(o);
            t.start();

        } catch(IOException ioe)
        {
            System.err.println("IOE IN OPLAYER MAIN" + ioe.getMessage());
            System.exit(1);
        }
    }
}

如代码所示,XPlayer 中的 toSync.notifyAll() 调用没有唤醒 OPlayer 线程,一旦 XPlayer 迈出了第一步,我就陷入了僵局

我相信只需要那 2 个 classes 就可以解决这个问题,但为了以防万一,这里有 classes Player Board 和 TTTServer: Class 玩家:

import java.net.*;
import java.io.*;

public class Player
implements Constants
{   
    protected static final Object toSync = new Object();

    protected Socket sock;
    protected BufferedReader stdin;
    protected BufferedReader in;
    protected PrintWriter out;

    protected String name;
    protected char mark;
    protected Board board;

    public Player() throws IOException
    {
        sock = new Socket("localhost",1298);
        stdin = new BufferedReader(new InputStreamReader(System.in));
        in = new BufferedReader(new 
                InputStreamReader(sock.getInputStream()));
        out = new PrintWriter(sock.getOutputStream(),true);

        System.out.println(WELCOME);
        System.out.println("Please Enter your name:");
        name = stdin.readLine();
        board = new Board();
    }

    public Cell makeMove() throws IOException
    {
        board.display();

        int row = -1;
        int col = -1;
        do 
        {
            while (row < 0 || row > 2)
            {   
                System.out.println
                    (name + ", what row would you like your next move to be in?");
                row = Integer.parseInt(stdin.readLine());
                if (row < 0 || row > 2)
                    {System.out.println("Invalid entry! Try again...");}
            }
            while (col < 0 || col > 2)
            {   
                System.out.println
                    (name + ", what column would you like your next move to be in?");
                col = Integer.parseInt(stdin.readLine());
                if (col < 0 || col > 2)
                    {System.out.println("Invalid entry! Try again...");}
            }

            if (board.getMark(row, col) != SPACE_CHAR)
                {System.out.println("That spot is already taken Try again...");}

        } while (board.getMark(row,col) != SPACE_CHAR);

        return new Cell(row,col,mark);
    }

    public void endGame()
    {
        if (board.xWins() == 1)     {System.out.println(END + XWIN);}
        if (board.oWins() == 1)     {System.out.println(END + OWIN);}
        else            {System.out.println(END + " It was a tie!!");}
    }
}

Class TTT服务器:

import java.net.*;
import java.io.*;

public class TTTServer
implements Constants
{
    public static void main(String[] args)
    {
        try
        {
            ServerSocket ss = new ServerSocket(1298,2);
            System.out.println("The Server is running...");
            Socket sock;

            Board board = new Board();

            sock = ss.accept();
            sock = ss.accept();

            BufferedReader in = new BufferedReader(new 
                    InputStreamReader(sock.getInputStream()));
            PrintWriter out = new PrintWriter(sock.getOutputStream(),true);

            do
            {
                String moveString;

                do {moveString = in.readLine();}
                while (moveString == null); 

                Cell move = Cell.split(moveString);
                board.addMark(move.row(), move.col(), move.mark());

                out.println(moveString);

            } while(!board.isOver());

            in.close();
            out.close();
            ss.close();
            sock.close();

        } catch(IOException ioe)
        {
            System.out.println("IOE IN TTTSERVER " + ioe.getMessage());
            System.exit(1);
        }

    }
}

Class 董事会:

public class Board 
implements Constants
{
    /**
     * A 2D char array stores the game board and 
     * the total number of marks
     */
    private char theBoard[][];
    private int markCount;
    /**
     * Default constructor initializes the array and fills it with
     * SPACE_CHARs from the Constants interface
     */
    public Board() 
    {
        markCount = 0;
        theBoard = new char[3][];
        for (int i = 0; i < 3; i++) {
            theBoard[i] = new char[3];
            for (int j = 0; j < 3; j++)
                theBoard[i][j] = SPACE_CHAR;
        }
    }
    /**
     * Getter for the mark at the location specified by the arguments
     * 
     * @param row
     * @param column
     * 
     * @return mark
     */
    public char getMark(int row, int col) 
        {return theBoard[row][col];}
    /**
     * Getter for the number of moves which have been made thus far
     * 
     * @return markCount
     */
    public int getMarkCount()       {return markCount;}
    /**
     * @return true if the game is over, otherwise false
     */
    public boolean isOver()
    {
        if (xWins() == 1 || oWins() == 1 || isFull())
            {return true;}

        return false;
    }
    /**
     * @return true if the board has been completely filled with 
     * X_CHARs and O_CHARs from Constants interface, else false
     */
    public boolean isFull() 
        {return markCount == 9;}
    /**
     * Runs checkWinner on LETTER_X from Constants interface
     * 
     * @return true if X has won, else false
     */
    public int xWins() 
        {return checkWinner(LETTER_X);}
    /**
     * Runs checkWinner on LETTER_O from Constants interface 
     * 
     * @return true if O has won, else false
     */
    public int oWins() 
        {return checkWinner(LETTER_O);}
    /**
     * Uses the formatting helper methods to display the board 
     * in the console
     */
    public void display() 
    {
        displayColumnHeaders();
        addHyphens();
        for (int row = 0; row < 3; row++) {
            addSpaces();
            System.out.print("    row " + row + ' ');
            for (int col = 0; col < 3; col++)
                System.out.print("|  " + getMark(row, col) + "  ");
            System.out.println("|");
            addSpaces();
            addHyphens();
        }
    }
    /**
     * Add the mark in the last argument to the location specified by the 
     * first two arguments
     * 
     * @param row
     * @param column
     * @param mark
     */
    public void addMark(int row, int col, char mark) 
    {
        theBoard[row][col] = mark;
        markCount++;
    }
    /**
     * Clears the board by replacing all marks with 
     * SPACE_CHARs from the Constants interface
     */
    public void clear() 
    {
        for (int i = 0; i < 3; i++)
            for (int j = 0; j < 3; j++)
                theBoard[i][j] = SPACE_CHAR;
        markCount = 0;
    }
    /**
     * Checks if the player with the argument mark has won the game
     * 
     * @param mark
     * 
     * @return true if the game was won, else false
     */
    int checkWinner(char mark) {
        int row, col;
        int result = 0;

        for (row = 0; result == 0 && row < 3; row++) {
            int row_result = 1;
            for (col = 0; row_result == 1 && col < 3; col++)
                if (theBoard[row][col] != mark)
                    row_result = 0;
            if (row_result != 0)
                result = 1;
        }


        for (col = 0; result == 0 && col < 3; col++) {
            int col_result = 1;
            for (row = 0; col_result != 0 && row < 3; row++)
                if (theBoard[row][col] != mark)
                    col_result = 0;
            if (col_result != 0)
                result = 1;
        }

        if (result == 0) {
            int diag1Result = 1;
            for (row = 0; diag1Result != 0 && row < 3; row++)
                if (theBoard[row][row] != mark)
                    diag1Result = 0;
            if (diag1Result != 0)
                result = 1;
        }
        if (result == 0) {
            int diag2Result = 1;
            for (row = 0; diag2Result != 0 && row < 3; row++)
                if (theBoard[row][3 - 1 - row] != mark)
                    diag2Result = 0;
            if (diag2Result != 0)
                result = 1;
        }
        return result;
    }

    /**
     * The final three helper methods are called by display 
     * to format the board properly in the console
     */
    void displayColumnHeaders() {
        System.out.print("          ");
        for (int j = 0; j < 3; j++)
            System.out.print("|col " + j);
        System.out.println();
    }

    void addHyphens() {
        System.out.print("          ");
        for (int j = 0; j < 3; j++)
            System.out.print("+-----");
        System.out.println("+");
    }

    void addSpaces() {
        System.out.print("          ");
        for (int j = 0; j < 3; j++)
            System.out.print("|     ");
        System.out.println("|");
    }
}

这是你的错误:

Both Xplayer and OPlayer have main methods, so that they can be run concurrently.

如果您运行宁两个main()方法,它们不是运行宁"concurrently";它们是完全独立的过程。这意味着没有共享线程、变量、对象、通知等。如果你想共享状态,你需要从一个单一的 main() 方法开始一切:

class StarterClass {
    public static void main(String[] args)
    {
        // start XPlayer thread
        try
        {
            XPlayer x = new XPlayer();
            Thread t = new Thread(x);
            t.start();

        } catch(IOException ioe)
        {
            System.err.println
                ("IOE IN XPLAYER MAIN " + ioe.getMessage());
            System.exit(1);
        }

        // start OPlayer thread
        try
        {
            OPlayer o = new OPlayer();
            Thread t = new Thread(o);
            t.start();

        } catch(IOException ioe)
        {
            System.err.println("IOE IN OPLAYER MAIN" + ioe.getMessage());
            System.exit(1);
        }
    }
}

如果您的意图是让每个 Player 运行 作为一个单独的客户端同时轮流交替,线程同步是不适合这项工作的工具。您需要在服务器和客户端之间实现自定义消息传递,以保持它们同步。