【问题标题】:How do I start one thread for my code and one for a JavaFX Application?如何为我的代码启动一个线程,为 JavaFX 应用程序启动一个线程?
【发布时间】:2016-10-01 11:16:37
【问题描述】:

我正在尝试使用 JavaFX 运行程序。如果我使用的是 Swing,我会使用 main 方法启动一个类,并让它构建 GUI 类。这会给我 2 个线程,一个应用程序的普通线程和 EventQueue。这样可以防止阻止 UI 工作。

所以,我尝试让静态 main 方法中创建的类构造 Application 类,然后启动它。我得到了一个 RuntimeException,因为调用启动方法的程序不是 Application 的子类。

有没有办法分离线程,或者一切都必须在应用程序类的线程内工作?

【问题讨论】:

    标签: java multithreading javafx-8


    【解决方案1】:

    在 JavaFX 中,我猜为了防止在许多 Swing 应用程序中出现的非常常见的线程错误,启动过程受到限制,因此(或多或少)唯一的​​做事方式迫使您执行FX 应用线程上的 UI 代码:

    public class MyAppStartClass extends Application {
    
        @Override
        public void start(Stage primaryStage) {
            // this method will be executed on the FX Application Thread
    
            // load UI and display it here....
        }
    
    }
    

    在 Oracle JRE 中,执行 java MyAppStartClass 将(与常规 Java 应用程序相反)导致创建 MyAppStartClass 的实例,启动 FX 应用程序线程,以及 start 的方法创建要在 FX 应用程序线程上执行的实例。 (除此之外还有更多内容,但这是基本要点。)

    如果您想支持不知道如何执行 JavaFX 应用程序的环境(包括许多 IDE),您可以添加一个 main 方法来强制执行此操作,只需调用静态 Application.launch() 方法:

    public class MyAppStartClass extends Application {
    
        @Override
        public void start(Stage primaryStage) {
            // this method will be executed on the FX Application Thread
    
            // load UI and display it here....
        }
    
        // to support non-JavaFX-aware environments:
        public static void main(String[] args) {
            launch(args);
        }
    
    }
    

    请注意launch 的重载形式,您可以在其中指定Application 子类,因此您可以拥有不同的主类(用例很少):

    public class MainClass {
        public static void main(String[] args) {
            Application.launch(MyAppStartClass.class, args);
        }
    }
    

    launch 方法有两个重要特性需要注意:

    1. 每个 JVM 生命周期只能调用一次。再次调用它会引发异常。
    2. 它会一直阻塞,直到 JavaFX 平台退出。

    如果你想让一个线程在FX Application Thread之外工作,最简单的方法是直接从start方法启动它:

    public class MyAppStartClass extends Application {
    
        @Override
        public void start(Stage primaryStage) {
    
            // start a background thread to do background stuff:
    
            new Thread(() -> {
                // background work...
            }).start();
    
    
            // UI work...
        }
    
    }
    

    如果您将此与 Swing 应用程序的标准启动进行比较:

    public class MySwingApp {
    
        public static void main(String[] args) {
    
            SwingUtilities.invokeLater(() -> {
                // UI work...
            });
    
            // background work...
        }
    }
    

    与 Swing 相比,该过程有点颠倒。在 Swing 中,您(应该)显式地让启动方法(main)指定 UI 在 AWT 事件调度线程上运行,并且您可以在执行 main 的“当前”线程中执行其他操作。在 JavaFX 中,启动方法 (start) 在 UI 线程上执行,如果您想在另一个线程上执行操作,则显式启动一个。

    其他规则几乎相同:作为场景图一部分的 UI 元素只能在 UI 线程上进行修改等。注意 JavaFX 有一个特定的concurrency API 用于在后台线程中管理任务和调度 UI 更新在 FX 应用程序线程上。

    旁白:

    理论上我想你可以这样做:

    public class MainClass {
        public static void main(String[] args) {
            new Thread(() -> Application.launch(MyAppStartClass.class, args)).start();
    
            // do "background thread" work here in the (now free) main thread
        }
    }
    

    但由于该成语与通常的设置相去甚远,因此我不推荐它。请特别注意,您不应该直接在 Application 子类中使用这样的 main 方法,因为(我认为)Oracle JRE(或知道如何运行 JavaFX 应用程序的环境)可能会忽略 main 方法并启动您的启动方法如本文顶部所述。

    【讨论】:

    • 另请注意:如果“我的代码的一个线程”仅用于初始化,而不是持续的后台处理,那么您可以使用应用程序的 init() 方法,该方法将在调用launch() 的线程(通常是Java 主线程)。它只对初始化有用,因为应用程序在初始化完成之前不会启动。
    【解决方案2】:

    它与 Swing 并没有什么不同,FX 和 Swing 都使用自己的 Event dispatch thread。

    以这个同时运行 3 个应用程序的示例为例:

    import java.util.Scanner;
    
    import javax.swing.JButton;
    import javax.swing.JFrame;
    import javax.swing.SwingUtilities;
    
    import javafx.application.Application;
    import javafx.scene.Scene;
    import javafx.scene.control.Button;
    import javafx.scene.layout.StackPane;
    import javafx.stage.Stage;
    
    public class TripleFun {
    
        public static class CmdApplication {
            public void doThings() {
                Scanner in = new Scanner(System.in);
                while (true) {
                    String line = in.nextLine();
                    if ("quit".equals(line))
                        return;
                    println("Hello: " + line);
                }
            }
        }
    
        public static class SwingApplication extends JFrame {
            public SwingApplication() {
                setTitle("Swing");
                setSize(200, 200);
                setVisible(true);
                JButton button = new JButton("Ok.");
                button.addActionListener(e -> println("Swing button"));
                add(button);
                setDefaultCloseOperation(DISPOSE_ON_CLOSE);
                println("Swing Thread");
            }
        }
    
        public static class FxApplication extends Application {
    
            @Override
            public void start(Stage primaryStage) throws Exception {
                println("Fx Thread");
                StackPane root = new StackPane();
                Button button = new Button();
                button.setText("Ok.");
                button.setOnAction(e -> println("Fx button"));
                root.getChildren().add(button);
                Scene scene = new Scene(root, 300, 250);
                primaryStage.setTitle("FX");
                primaryStage.setScene(scene);
                primaryStage.show();
            }
    
        }
    
        public static void main(String[] args) {
            SwingUtilities.invokeLater(() -> {
                new SwingApplication();
                println("swing invoke done.");
            });
            println("Started Swing.");
    
            new Thread(() -> {
                println("FX launch thread started.");
                Application.launch(FxApplication.class, args);
                println("FX launch thread done.");
            }).start();
            println("Started Fx.");
    
            new Thread(() -> {
                println("Cmd thread started.");
                new CmdApplication().doThings();
                println("Cmd thread done.");
            }).start();
            println("Started cmd. initial thread done.");
        }
    
        public static void println(String msg) {
            System.out.println(Thread.currentThread().getName() + " - " + msg);
        }
    }
    

    有一些输出,比如

    main - Started Swing.
    main - Started Fx.
    Thread-1 - FX launch thread started.
    main - Started cmd. initial thread done.
    Thread-2 - Cmd thread started.
    AWT-EventQueue-0 - Swing Thread
    AWT-EventQueue-0 - swing invoke done.
    JavaFX Application Thread - Fx Thread
    AWT-EventQueue-0 - Swing button
    AWT-EventQueue-0 - Swing button
    JavaFX Application Thread - Fx button
    JavaFX Application Thread - Fx button
    ok
    Thread-2 - Hello: ok
    ok
    Thread-2 - Hello: ok
    quit
    Thread-2 - Cmd thread done.
    Thread-1 - FX launch thread done.
    

    具有main 方法的初始线程几乎立即结束,每个子应用程序继续在它们自己的线程上运行。一旦所有线程都完成,整个应用程序就会结束。

    关于 JavaFX 唯一棘手的一点是它的 launch 方法会阻塞,直到应用程序(在它自己的线程中运行)完成。您可以让它接管最初的 main 线程并在新线程中运行您的其他代码,或者将启动移动到新线程以保持主线程空闲。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-03-17
      • 2013-04-21
      • 2012-02-08
      • 2014-11-05
      • 2020-11-09
      • 2019-01-03
      • 1970-01-01
      相关资源
      最近更新 更多