一起使用 Platform.exit() 和 System.exit(int)

Posted

技术标签:

【中文标题】一起使用 Platform.exit() 和 System.exit(int)【英文标题】:Using Platform.exit() and System.exit(int) together 【发布时间】: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),那么这可能会导致一些竞争条件,其中两个线程试图关闭同一个应用程序。

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

【问题讨论】:

【参考方案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(); 

【讨论】:

以上是关于一起使用 Platform.exit() 和 System.exit(int)的主要内容,如果未能解决你的问题,请参考以下文章

JavaFX-Platform&Screen

总的cpu使用率等于us和sy之和

将system2与包含&符号的参数一起使用(&)

输入在 do-while 循环中无法与 Scanner 类一起使用

SY-SUBRC

SY-SUBRC 的含义