JavaFX 应用程序线程变慢然后卡住

标签 java multithreading concurrency javafx-2

我有一个多线程 JavaFX 应用程序。我有一堆后台线程正在调用此方法以从应用程序线程更新主 UI。

public void writeToActivityLog(String message){

    class ThreadedTask implements Runnable {

        private String message;

        public ThreadedTask(String message){
            this.message = message;
        }

        @Override
        public void run() {
            try{
                //delete older text when there are more than 200 lines and append new text

                String text = outputLog.getText();
                String[] lines = text.split("\r\n");
                String newText = "";
                for(int i = 0; i < lines.length; i++){
                    if(i >= 200 || lines.length < 200){
                        newText += lines[i];
                    }
                }
                outputLog.setText(newText);
                outputLog.appendText(message + "\r\n");

                //scroll to the bottom of the log
                outputLog.setScrollTop(Double.MIN_VALUE);

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

    ThreadedTask thread = new ThreadedTask(message);
    Platform.runLater(thread);

}

一开始,这个方法很管用。但是进入程序几秒钟后,它开始变慢,几秒钟后整个 UI 卡住并停止响应用户输入(但它不会触发 Windows“此程序未响应”对话框)。但是,查看应用程序日志文件和我的 IDE 控制台,后台线程仍在执行并且似乎运行良好。

对于可以排队的 Platform.runLater() 请求的数量是否有一些限制?或者有什么方法可能会导致内存泄漏导致主应用程序线程终止但对后台线程没有任何作用?我对多线程编程还是很陌生,所以我不知道该想些什么。

我也知道 JavaFX 有另一个称为服务的并发工具,但我找不到任何关于何时以及为什么我应该使用它们而不是 Platform.runLater() 的解释。我编写过其他 JavaFX 应用程序,使用 Platform.runLater() 时从未遇到过任何问题。

编辑:

非常感谢下面的两个答案。问题不在于 JavaFX 或线程本身,而在于普通的错误代码。这是固定代码,为了完成:

public void writeToActivityLog(String message){
    //Create a new thread to update the UI so that it doesn't freeze due to concurrency issues
    class ThreadedTask implements Runnable {

        private String message;

        public ThreadedTask(String message){
            this.message = message;
        }

        @Override
        public void run() {
            outputLog.setText(message);

            //scroll to the bottom of the log
            outputLog.setScrollTop(Double.MIN_VALUE);
        }
    }

    String wholeMessage = new StringBuilder().append(outputLog.getText()).append(message).toString();
    //modify the text of the output log so that it is 200 lines or less
    StringBuilder newText = new StringBuilder();
    String[] lines = wholeMessage.split("\r\n");

    for (int i=Math.max(0, lines.length - 200); i<lines.length; i++) {
        newText.append(new StringBuilder().append(lines[i]).append("\r\n").toString());
    }

    ThreadedTask thread = new ThreadedTask(newText.toString());
    Platform.runLater(thread);
}

最佳答案

一些建议。

首先,这只是一般的 Java 事情,在这样的循环中通过串联来构建 String 是一个非常糟糕的主意。在许多情况下,现代编译器会为您修复此问题,但其中的 if 子句使得这样做更难,并且您不太可能自动获得此优化。您应该进行以下修改:

// String newText = "" ;
StringBuilder newText = new StringBuilder();
// ...
// newText += lines[i] ;
newText.append(lines[i]);
// ...
// outputLog.setText(newText);
outputLog.setText(newText.toString());

第二点是 JavaFX 并发问题。在这里创建线程并没有真正节省任何东西,因为您只是创建线程,然后通过将其直接传递给 Platform.runLater(...) 来安排它在 FX 应用程序线程上运行。因此,无论如何,整个 run() 方法只是在 FX 应用程序线程上执行。 (您的代码中没有后台线程!)

有两条规则需要遵守: 1. 仅更新 FX 应用程序线程上的“Activity ”节点 2. 不要在该线程上执行任何长时间运行的任务

javafx.concurrency 中的 Task 类是一个 Runnable(实际上是一个 Callable,更灵活一点),它提供了一些有用的生命周期方法,保证在 FX Application Thread 上运行。因此,您可以使用 Task 来计算新的日志文本,返回新的日志文本,然后在完成后使用 setOnSucceeded(..) 更新 UI。这看起来像:

class UpdateLogTask extends Task<String> { // a Task<T> has a call() method returning a T
    private final String currentLog ;
    private final String message ;
    public UpdateLogTask(String currentLog, String message) {
      this.currentLog = currentLog ;
      this.message = message ;
    }
    @Override
    public String call() throws Exception {
      String[] lines = currentLog.split("\r\n");
      StringBuilder text = new StringBuilder();
      for (int i=0; i<lines.length; i++) {
        if (i>=200 || lines.length < 200) {
          text.append(lines[i]);
        }
      }
      text.append(message).append("\r\n");
      return text.toString() ;
    }
}
final Task<String> updateLogTask = new UpdateLogTask(outputLog.getText(), message);
updateLogTask.setOnSucceeded(new EventHandler<WorkerStateEvenet>() {
    @Override
    public void handle(WorkerStateEvent event) {
      outputLog.setText(updateLogTask.getValue());
    }
});
Thread t = new Thread(updateLogTask);
t.setDaemon(true); // will terminate if JavaFX runtime terminates
t.start();

最后,我认为如果您想要最后 200 行文本,那么您的逻辑是错误的。尝试

for (int i=Math.max(0, lines.length - 200); i<lines.length; i++) {
  text.append(lines[i]);
}

关于JavaFX 应用程序线程变慢然后卡住,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20577410/

相关文章:

java - 高效执行独占执行

java - Google Cloud Messaging 监听广播接收器

java - 运行具有单个超时的多个任务(线程)的最佳实践是什么(在所有线程中运行最多 30 秒)?

java - 如何在集群上使用 JVM 程序? (比如停产的 cJVM/JavaSplit)

c++ - 高效实现多线程排序算法的关键是什么?天真的实现不能正常工作

java - 将 JSON 映射到 Java 中的数组/对象 - Android

java - hibernate 2.1.6 还是 3.x?

multithreading - Spring Batch 线程安全的 Map 作业存储库

java - 同步方法中 ConcurrentHashMap 的并发级别

使用 slice 类型的输入和输出 channel 去并发工作例程