【问题标题】:Using Platform.exit() and System.exit(int) together一起使用 Platform.exit() 和 System.exit(int)
【发布时间】:2018-02-13 16:34:35
【问题描述】:

我想关闭带有指定返回码的 javafx 应用程序。浏览SO上的答案,我发现了以下成语:

Platform.exit();
System.exit(0); 

例如这里: Stop threads before close my JavaFX program
或在这里:JavaFX application still running after close

这两个方法一个接一个地执行,看起来我们在尝试复制一些动作。我会假设,如果Platform.exit() 成功,它不应该返回到调用System.exit(0) 的地方。但是,如果Platform.exit() 仅触发在另一个线程上运行的某些关闭操作,返回并且可以调用System.exit(0),那么这可能会导致一些竞争条件,其中两个线程试图关闭同一个应用程序。

那么,这个成语究竟是如何工作的呢?

【问题讨论】:

    标签: java multithreading javafx


    【解决方案1】:

    调用System.exit(...) 会终止Java 虚拟机。

    据我了解,调用 Platform.exit() 只是指示 JavaFX Toolkit 关闭,导致在 FX 应用程序线程上调用应用程序实例的 stop() 方法,并允许 FX 应用程序线程终止。这反过来又导致Application.launch() 返回。如果您在 main(...) 方法中使用惯用语:

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

    然后一旦launch() 返回,main() 方法就没有什么可做的了,并且没有(只要没有非守护线程正在运行)应用程序以正常方式退出。 Platform.exit() 在任何情况下都不会创建对 System.exit(...) 的调用:但是在某些情况下,它会允许 JVM 退出,因为它无事可做。

    如果你调用System.exit(...),JVM 基本上会立即退出。因此,例如,如果您在Application.launch() 之后的main(...) 方法中有代码,则该代码将在调用Platform.exit() 之后执行,但不会在调用System.exit(...) 之后执行。同样,如果您覆盖Application.stop(),则stop() 方法会在调用Platform.exit() 之后调用,而不是在调用System.exit(...) 之后调用。

    如果你有非守护线程在运行,Platform.exit() 不会强制关闭它们,但System.exit() 会。

    下面的例子应该证明这一点:

    import javafx.application.Application;
    import javafx.application.Platform;
    import javafx.scene.Scene;
    import javafx.scene.control.Button;
    import javafx.scene.layout.HBox;
    import javafx.stage.Stage;
    
    public class ExitTest extends Application {
    
        @Override
        public void stop() {
            System.out.println("Stop called");
        }
    
        @Override
        public void start(Stage primaryStage) {
            Button startThread = new Button("Start non-daemon thread");
            startThread.setOnAction(e -> new Thread(() -> {
                System.out.println("Starting thread");
                try {
                    Object lock = new Object();
                    synchronized(lock) {
                        lock.wait();
                    }
                } catch (InterruptedException exc) {
                    System.err.println("Interrupted");
                    Thread.currentThread().interrupt();
                } finally {
                    System.out.println("Thread complete");
                }
            }).start());
    
            Button exit = new Button("Simple Exit");
            exit.setOnAction(e -> {
                System.out.println("Calling Platform.exit()");
                Platform.exit();
            });
    
            Button forceExit = new Button("Force exit");
            forceExit.setOnAction(e -> {
                System.out.println("Calling Platform.exit():");
                Platform.exit();
                System.out.println("Calling System.exit(0):");
                System.exit(0);
            });
    
            Scene scene = new Scene(new HBox(5, startThread, exit, forceExit));
            primaryStage.setScene(scene);
            primaryStage.show();
        }
    
        public static void main(String[] args) {
            launch(args);
            System.out.println("launch() complete");
        }
    }
    

    通常建议您通过调用Platform.exit() 退出JavaFX 应用程序,这样可以正常关闭:例如,如果您需要任何“清理”代码,您可以将其放入stop() 方法中Platform.exit() 将允许它被执行。如果你正在运行必须终止的后台线程,要么让它们成为守护线程,要么通过执行器服务执行它们,然后通过stop() 方法关闭执行器服务。这是对使用此技术的上述示例的修改。

    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.TimeUnit;
    
    import javafx.application.Application;
    import javafx.application.Platform;
    import javafx.scene.Scene;
    import javafx.scene.control.Button;
    import javafx.scene.layout.HBox;
    import javafx.stage.Stage;
    
    public class ExitTest extends Application {
    
        private final ExecutorService exec = Executors.newCachedThreadPool();
    
        @Override
        public void stop() throws InterruptedException {
            System.out.println("Stop called: try to let background threads complete...");
            exec.shutdown();
            if (exec.awaitTermination(2, TimeUnit.SECONDS)) {
                System.out.println("Background threads exited");
            } else {
                System.out.println("Background threads did not exit, trying to force termination (via interruption)");
                exec.shutdownNow();
            }       
        }
    
        @Override
        public void start(Stage primaryStage) {
            Button startThread = new Button("Start non-daemon thread");
            startThread.setOnAction(e -> { 
                exec.submit( () -> {
                    System.out.println("Starting thread");
                    try {
                        // just block indefinitely:
                        Object lock = new Object();
                        synchronized(lock) {
                            lock.wait();
                        }
                    } catch (InterruptedException exc) {
                        System.out.println("Interrupted");
                        Thread.currentThread().interrupt();
                    } finally {
                        System.out.println("Thread complete");
                    }
                });
            });
    
            Button exit = new Button("Simple Exit");
            exit.setOnAction(e -> {
                System.out.println("Calling Platform.exit()");
                Platform.exit();
            });
    
    
            Scene scene = new Scene(new HBox(5, startThread, exit));
            primaryStage.setScene(scene);
            primaryStage.show();
        }
    
        public static void main(String[] args) {
            launch(args);
            System.out.println("launch() complete");
        }
    }
    

    如果你想使用Platform.exit() 来优雅地关闭,并且你想从System.exit(...) 返回一个值,下面的方法应该可以工作。请注意,这并不是真正推荐的做法:在生产代码中,您根本不应该真正依赖支持进程退出代码的平台。

    public class App extends Application {
    
        private static int exitCode = 0 ;
    
        public static exit(int exitCode) {
            App.exitCode = exitCode ;
            Platform.exit();
        }
    
        @Override
        public void start(Stage primaryStage) {
            // ...
    
            someThing.addEventHander(someEventType, e -> App.exit(42));
    
            // ...
        }
    
        @Override
        public void stop() {
            // cleanup code...
        }
    
        public static void main(String[] args) {
            Application.launch(args);
            System.exit(exitCode);
        }
    }
    

    【讨论】:

    • 感谢您的解释。我想知道您在生产代码中写了关于“不推荐的做法”的内容。这是否意味着生产代码不应该返回除 0 之外的任何其他退出代码?
    • @PiotrG 你不应该依赖退出代码被传播回给启动 JVM 的人,因为它实际上是一个依赖于平台的特性。
    • 嗯 ...如果您知道您的生产应用程序只会在(比如说)Linux 上运行,那么假设类似 Linux 的退出代码处理并非不合理。
    【解决方案2】:

    在这种情况下,Doc 是你的朋友:

    调用System.exit(0) 将终止JVM

    终止当前运行的 Java 虚拟机。论据 作为状态码;按照惯例,一个非零状态码 表示异常终止。该方法调用中的exit方法 类运行时。此方法永远不会正常返回。

    Platform.exit() 将终止 FX 应用程序

    导致 JavaFX 应用程序终止。如果调用此方法 调用 Application start 方法后,JavaFX 启动器 将调用 Application stop 方法并终止 JavaFX 应用程序线程。然后启动器线程将关闭。如果有 没有其他正在运行的非守护线程,Java VM 将 出口。如果从 Preloader 或 Application 调用此方法 init 方法,则应用程序停止方法可能不会被调用。

    【讨论】:

    • 嗯,你引用的内容并没有回答我的问题。在非守护线程的情况下运行Platform.exit()应该继续关闭JVM,那么它如何返回到调用System.exit(0)的地方呢?如果它返回,那么这两种方法都会竞相终止 JVM。
    • 对不起,我没有完全关注你。 “两句”是指“第二句”,即:“调用 System.exit(0) 将终止 JVM”?是的,System.exit(0) 会终止 JVM,但首先调用 Platform.exit() 也会导致退出 JVM。而且我仍然不知道Platform.exit()如何返回到System.exit()被调用的地方。
    • 最后一行加一个。有正确的方法可以做到这一点;然后是黑客。这些都是黑客。
    • 好的,那么关闭返回特定状态代码的 JavaFX 应用程序的正确方法是什么?
    • @BoristheSpider Platform.exit() 是黑客吗?它允许正常关闭,让 FX 应用程序线程完成,并调用 stop() 回调方法(假设应用程序具有 start()ed),从而允许清理资源。
    【解决方案3】:

    另一种避免黑客攻击的方法是检查是否有任何阶段打开并使用 Stage.close() 关闭它们,然后然后调用 Platform.exit()。例如,在一个有两个窗口的应用程序中:

                if (secondWindow != null) secondWindow.close();
                Platform.exit(); 
    

    【讨论】:

      猜你喜欢
      • 2018-07-20
      • 1970-01-01
      • 2017-10-23
      • 1970-01-01
      • 1970-01-01
      • 2013-10-05
      • 2013-12-27
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多