有没有办法在 JavaFX 中跨不同的 class 文件使用线程?

Is there a way to use a thread across different class files in JavaFX?

好的,所以我对 Java 编程并不陌生,但我对在我的 Java 程序中使用线程很陌生。我在学校,刚刚完成有关线程和 Java 网络的章节。我正在编写一个客户端 GUI 程序,用于将贷款信息(年利率、年数和贷款金额)发送到服务器。服务器有自己的 GUI 并计算每月还款额和贷款总付款额并将其发送回客户端并显示给用户并更新服务器 GUI。

书上有这段代码作为例子:

public class Server extends Application {
/** Variables */
private TextArea textArea = new TextArea();
private double rate;
private int year;
private double loan;

public void start(Stage serverStage)
{
    // Creating server GUI
    Scene scene = new Scene(new ScrollPane(textArea), 400, 200);
    serverStage.setTitle("Server");
    serverStage.setScene(scene);
    serverStage.show();

    new Thread(() ->{

        try
        {
            // create server socket
            ServerSocket serverSocket = new ServerSocket(8000);
            textArea.appendText("Server started at " + new Date() + "\n");

            while(true)
            {
                // listen for a connection request
                Socket socket = serverSocket.accept();

                Platform.runLater(() -> {
                    InetAddress inetAddress = socket.getInetAddress();
                    textArea.appendText("Connected to " + inetAddress.getHostAddress() + " at " + new Date() + "\n");
                });


                // create and start a new thread for every connection
                new Thread(new HandleAClient(socket)).start();

            }
        }
        catch(IOException ex)
        {
            ex.printStackTrace();
        }
    }).start();
}

class HandleAClient implements Runnable {

    private Socket socket; // A connected socket
    private double rate;
    private int year;
    private double loan;

    /** costruct a thread */
    public HandleAClient(Socket socket)
    {
        this.socket = socket;
    }

    /** run a thread */
    public void run(){
        try
        {
            // create data input and output streams
            DataInputStream inputFromClient = new DataInputStream(socket.getInputStream());
            DataOutputStream outputToClient = new DataOutputStream(socket.getOutputStream());

            // continuously serve the client
            while(true) {
                // read data from client

                rate = inputFromClient.readDouble();
                year = inputFromClient.readInt();
                loan = inputFromClient.readDouble();


                // calculate monthly payment of loan and total payment

                outputToClient.writeDouble(calculateMonthlyPayment(rate, year, loan));
                outputToClient.writeDouble(calculateTotalPayment(rate, year, loan));

                Platform.runLater( () -> {
                    textArea.appendText("The rate is : " + rate + "\n");
                    textArea.appendText("The number of years is: " + year + "\n");
                    textArea.appendText("Loan amount is: " + loan + "\n\n");});




            }
        }
        catch(IOException e)
        {
            e.printStackTrace();
        }
    }
}

// calculateMonthlyPayment method calculates the monthly payment of a loan given
// the required information

public double calculateMonthlyPayment(double interestRate, int years, double loanAmt)
{
    double monthlyRate;
    int termInMonths;
    double monthlyPayment;

    // Convert the interest rate to a decimal
    interestRate = interestRate / 100;

    // convert annual interest rate to monthly interest rate
    monthlyRate = interestRate / 12;

    // calculate the term in months which is years * 12
    termInMonths = years * 12;

    monthlyPayment = (loanAmt*monthlyRate) / (1-Math.pow(1+monthlyRate, -termInMonths));

    return monthlyPayment;
}



// method that calculates and returns the total payment of the loan
public double calculateTotalPayment(double rate, int year, double loan)
 {
    double totalPayment;
    double monthlyPay;

    monthlyPay = calculateMonthlyPayment(rate, year, loan);


    totalPayment = monthlyPay * 12 * year;

    return totalPayment;
  }

}

正如您在示例代码中看到的那样,他们(本书的作者)使用新线程来附加服务器 GUI 的文本。然而,为了能够处理多个客户端,在 while 循环内创建了一个新线程来处理每个单独的客户端。

我尝试将 HandleAClient class 创建为单独的 Java class 而不是将其插入服务器 class 但这导致服务器 GUI 未更新使用 Platform.runLater 代码

Platform.runLater( () -> {
                    textArea.appendText("The rate is : " + rate + "\n");
                    textArea.appendText("The number of years is: " + year + "\n");
                    textArea.appendText("Loan amount is: " + loan + "\n\n");});

所以我的问题是:为什么当 HandleAClient class 位于服务器 class 内部时它可以工作,而当 HandleAClient class 位于单独的 Java class 文件扩展服务器?我认为它必须对线程做些什么?我要改变什么才能让 HandleAClient class 在它自己的 Java class 文件中?

我很好奇并试图对线程的工作原理有一个很好的理解。提前谢谢你。

更新 这是对我不起作用的独立 class。我扩展了服务器 class 并在服务器 class.

中保护了 TextArea 字段
class HandleAClient extends Server implements Runnable {

private Socket socket; // A connected socket
private double rate;
private int year;
private double loan;


public HandleAClient(Socket socket)
{
    this.socket = socket;
}

/** run a thread */
public void run(){
    try
    {
        // create data input and output streams
        DataInputStream inputFromClient = new DataInputStream(socket.getInputStream());
        DataOutputStream outputToClient = new DataOutputStream(socket.getOutputStream());

        // continuously serve the client
        while(true) {
            // read data from client

            rate = inputFromClient.readDouble();
            year = inputFromClient.readInt();
            loan = inputFromClient.readDouble();


            // calculate monthly payment of loan and total payment

            outputToClient.writeDouble(calculateMonthlyPayment(rate, year, loan));
            outputToClient.writeDouble(calculateTotalPayment(rate, year, loan));

            Platform.runLater( () -> {
                textArea.appendText("The rate is : " + rate + "\n");
                textArea.appendText("The number of years is: " + year + "\n");
                textArea.appendText("Loan amount is: " + loan + "\n\n");});

        }
    }
    catch(IOException e)
    {
        e.printStackTrace();
    }
  }
}

Platform.runLater 中的代码不会像 class 在服务器 class 中时那样出现在服务器 GUI 中。我想了解为什么会发生这种情况。

您需要 HandleAClient 实例来更新属于创建它们的 Server 对象的文本区域:属于 Server 实例的 textArea 是UI 中显示的一个。通过您的设置,每个 HandleAClient 实例都有自己的 textArea,并且每个实例都更新自己的 textArea。当然,其中 None 曾经显示过。

HandleAClient 延长 Server 对我来说真的没有意义。您需要做的(无论您是否继承)是为 HandleAClient 提供一种更新属于 Server 的文本区域的方法。最简单(但不一定是最好)的方法就是将该文本区域传递给 HandleAClient 个实例:

class HandleAClient implements Runnable {

    private Socket socket; // A connected socket
    private double rate;
    private int year;
    private double loan;

    private final TextArea textArea ;

    public HandleAClient(Socket socket, TextArea textArea)
    {
        this.socket = socket;
        this.textArea = textArea ;
    }

    /** run a thread */
    public void run(){
        try
        {
            // create data input and output streams
            DataInputStream inputFromClient = new DataInputStream(socket.getInputStream());
            DataOutputStream outputToClient = new DataOutputStream(socket.getOutputStream());

            // continuously serve the client
            while(true) {
                // read data from client

                rate = inputFromClient.readDouble();
                year = inputFromClient.readInt();
                loan = inputFromClient.readDouble();


                // calculate monthly payment of loan and total payment

                outputToClient.writeDouble(calculateMonthlyPayment(rate, year, loan));
                outputToClient.writeDouble(calculateTotalPayment(rate, year, loan));

                Platform.runLater( () -> {
                    textArea.appendText("The rate is : " + rate + "\n");
                    textArea.appendText("The number of years is: " + year + "\n");
                    textArea.appendText("Loan amount is: " + loan + "\n\n");});

            }
        }
        catch(IOException e)
        {
            e.printStackTrace();
        }
    }
}

然后当然在 Server 中将其修改为

while(true)
{
    // listen for a connection request
    Socket socket = serverSocket.accept();

    Platform.runLater(() -> {
        InetAddress inetAddress = socket.getInetAddress();
        textArea.appendText("Connected to " + inetAddress.getHostAddress() + " at " + new Date() + "\n");
    });


    // create and start a new thread for every connection
    new Thread(new HandleAClient(socket, textArea)).start();

}

HandleAClientclass(负责通信)依赖引用TextArea(也就是UI)感觉有点不自然-具体的)。更自然的做法如下。

我可能会定义一个简单的 class 来表示利率、年份和贷款:

public class LoanData {

    private final double rate ;
    private final int year ;
    private final double loan ;

    public LoanData(double rate, int year, double loan) {
        this.rate = rate ;
        this.year = year ;
        this.loan = loan ;
    }

    public double getRate() {
        return rate ;
    }
    public int getYear() {
        return year ;
    }
    public double getLoan() {
        return loan ;
    }
}

然后给HandleAClientclass一个Consumer<LoanData>处理贷款数据:

class HandleAClient implements Runnable {

    private Socket socket; // A connected socket

    private final Consumer<LoanData> dataProcessor ;

    public HandleAClient(Socket socket, Consumer<LoanData> dataProcessor)
    {
        this.socket = socket;
        this.dataProcessor = dataProcessor ;
    }

    /** run a thread */
    public void run(){
        try
        {
            // create data input and output streams
            DataInputStream inputFromClient = new DataInputStream(socket.getInputStream());
            DataOutputStream outputToClient = new DataOutputStream(socket.getOutputStream());

            // continuously serve the client
            while(true) {
                // read data from client

                double rate = inputFromClient.readDouble();
                double year = inputFromClient.readInt();
                double loan = inputFromClient.readDouble();


                // calculate monthly payment of loan and total payment

                outputToClient.writeDouble(calculateMonthlyPayment(rate, year, loan));
                outputToClient.writeDouble(calculateTotalPayment(rate, year, loan));

                dataProcessor.accept(new LoanData(rate, year, loan));

            }
        }
        catch(IOException e)
        {
            e.printStackTrace();
        }
    }
}

现在在服务器中做

Consumer<LoanData> textAreaUpdater = 
    loanData -> Platform.runLater(() -> {
        textArea.appendText("The rate is : " + loanData.getRate() + "\n");
        textArea.appendText("The number of years is: " + loanData.getYear() + "\n");
        textArea.appendText("Loan amount is: " + loanData.getLoan() + "\n\n");
    });

new Thread(new HandleAClient(socket, textAreaUpdater)).start();

这使 UI 数据功能在 UI class.

中正确隔离