为什么JavaFX要求Platform.runLater在线程内的UI中更改任何内容?

时间:2015-06-19 13:18:06

标签: java multithreading swing javafx

这个简单的swing应用程序代码:

final JFrame jFrame = new JFrame();
final JLabel jLabel = new JLabel("Test");
jFrame.add(jLabel);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.pack();
jFrame.setLocationRelativeTo(null);
jFrame.setVisible(true);

Thread thread = new Thread(){
    @Override
    public void run() {
        while(true) {
            jLabel.setText("Time: " + System.currentTimeMillis());
            try { Thread.sleep(1000);} catch(Exception ex) {}
        }
    }
};
thread.setDaemon(true);
thread.start();

就像预期的那样运行。如果我们对JavaFX执行相同操作,则在将文本设置为标签时会出现错误java.lang.IllegalStateException: Not on FX application thread

我只是想知道,为什么它必须是这样的?为什么我们不能在我们想要的时候自由地做我们想做的事情?

1 个答案:

答案 0 :(得分:4)

因为JavaFX是单线程的,就像几乎所有的UI工具包一样。

您的Swing代码已损坏 - Swing也是单线程的,您应该在AWT事件处理线程上执行jLabel.setText(...),方法是将其包装在SwingUtilities.runLater(...);中,或者使用更高级别的API例如Timer。使用特定的JDK实现可能会在您的特定系统上运行,就像预期的那样运行,但是您编写的代码与您正在使用的工具包的契约相反,并且无法保证它将适用于其他JDK实现或其他平台。

JavaFX相对于Swing的一个主要改进是JavaFX通过在线程错误时抛出IllegalStateException来尽可能强制执行线程规则。在Swing中,您的代码在将来的某个时刻容易出现随机,不可预测的失败,而且根本没有任何警告。

在某些情况下,JavaFX不会强制执行规则,可能是因为它会对执行相关检查的性能产生负面影响,但您仍应遵循合同:对UI 的所有更改必须在UI线程上执行。

至于为什么JavaFX,Swing和其他UI工具包是这样编写的:这是一个非常复杂的问题。在大多数情况下,UI编程是事件驱动;因此,大多数情况下,您编写的用于修改场景图的代码是响应用户事件的;即UI线程无论如何都是修改UI的自然场所。因此,使UI工具包线程安全所带来的额外努力和性能可能不值得。尽管如此,已经进行了一些尝试来编写线程安全的UI工具包,但是它们从未运行良好:增加的同步负担会对性能产生不利影响,使其无法使用。这超出了我的理解范围,但从我所读到的问题来看,布局通常是在与事件处理相反的方向上完成的(即从窗口向下递归到子组件,而不是从&#34 ;叶子"组件通过父母到窗口)。因此,虽然处理布局或事件处理的同步是可行的,但为两者执行此操作变得非常昂贵。请参阅this进行(可能更准确)讨论。