GTKLookAndFeel 在 Ubuntu 20 上导致死锁

GTKLookAndFeel causes deadlock on Ubuntu 20

当运行输入以下代码时,我运行遇到了一个奇怪的问题:

public static void main(String[] args) throws Exception {
    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
    JFrame frame = new JFrame("Test");
    frame.setLocationRelativeTo(null);
    SwingUtilities.invokeAndWait(JFXPanel::new);
    frame.add(new JFXPanel());
    Platform.runLater(() -> {
        JPanel panel = new JPanel();
        System.exit(0);
    });
    frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    frame.setSize(200, 100);
    frame.setVisible(true);
}

首先,代码是用 OpenJDK-11 和 OpenJFX-11 编译的,在 Windows 中这个 运行 很好(即在 System.exit(0) 调用时退出) .

但是,如果我 运行 在 Linux 上执行此操作(具体来说 Ubuntu 20.04),对 new JPanel() 的调用将锁定线程,并且程序永远不会退出。注释掉 UIManager.setLookAndFeel 调用将使其再次正常工作。

我只是 运行遇到 com.sun.java.swing.plaf.gtk.GTKLookAndFeel 中的错误(这就是 SystemLookAndFeel returns)还是我在这里做了什么 wrong/unexpected?

am I doing something wrong/unexpected here?

是的,您做错了什么:您违反了Swing threading policy,其中规定

All Swing components and related classes, unless otherwise documented, must be accessed on the event dispatching thread.

这个答案会告诉你如何重构你的代码,让它遵守 (和其他)线程规则。我没有 Linux 环境来对此进行测试,因此可能在 JavaFX Linux 实现中也存在错误 and/or Swing 或 GTKLookAndFeel,但是 这至少为您提供了一种正确的测试方法。

您的 main 方法中的大部分代码应该在 AWT 事件分派线程上执行。您可以通过将其包装在 SwingUtilities.invokeLater(...).

中来实现此目的

例外情况是:

  1. Platform.runLater(...),它在 FX 应用程序线程上安排某些内容 运行,它本身可以从任何地方调用。但是,您在其中调用的 new JPanel() 应该 而不是 在 FX 应用程序线程上执行,它应该在 AWT 事件调度线程上执行。

  2. SwingUtilities.invokeAndWait(...) 不得 从 AWT 事件分派线程中调用。这会安排要在 AWT 事件分派线程上执行的代码,并暂停当前线程直到它完成。因此从 AWT 事件分派线程调用​​ SwingUtilities.invokeAndWait(...) 将导致死锁。代码

     SwingUtilities.invokeAndWait(JFXPanel::new);
    

    安排 new JFXPanel() 在 AWT 事件分派线程上执行,并阻塞当前线程直到完成。如果您已经在 AWT 事件调度线程中,只需调用

     new JFXPanel();
    

    实现同样的目标。

我知道您的代码试图创建一个最小的可重现示例,但并不清楚它是什么示例,因为那里的代码的目的完全不清楚。例如,您创建了两个 JFXPanel 个实例,您丢弃了第一个实例。然后你创建了一个 JPanel,你同样故意在不正确的线程上丢弃它。

我没有 Linux 环境来测试它,但您发布的代码的最接近“正确”版本是这样的。如果此代码仍然挂起,我相信库实现中某处存在错误。

public static void main(String[] args) throws Exception {

    // We are not on the AWT event dispatch thread, so if we
    // want to create and access Swing components, we must wrap
    // that code inside SwingUtilities.invokeLater(...):

    SwingUtilities.invokeLater(() -> {
        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        JFrame frame = new JFrame("Test");
        frame.setLocationRelativeTo(null);

        // We must not call SwingUtilities.invokeAndWait(...)
        // from the event dispatch thread, so remove this:
        // SwingUtilities.invokeAndWait(JFXPanel::new);

        // To create new JFXPanel and block until the call is
        // complete, we just create it in the usual way (since
        // we're already on the event dispatch thread)
        new JFXPanel();

        frame.add(new JFXPanel());
        Platform.runLater(() -> {
            // This code block is executed on the FX Application Thread
            // To create a swing component, we must wrap the
            // code in SwingUtilities.invokeLater(...), so it's
            // executed on the Event Dispatch Thread:
            SwingUtilities.invokeLater(() -> {
                JPanel panel = new JPanel();
                System.exit(0);
            });
        });
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.setSize(200, 100);
        frame.setVisible(true);
    });

}

顺便说一句,请注意外观只会影响 Swing 组件;它不会影响 JavaFX 控件,其外观和行为由 JavaFX 管理(默认情况下由默认 CSS 样式表)。因此,您在 JFXPanel 中放置的任何内容都不会受到外观的影响。

最后,您不应混用多个 UI 工具包,除非您有非常令人信服的理由这样做。如您所见,使用两个独立的 single-threaded 工具包具有挑战性。