其他线程中的繁忙循环延迟了EDT处理

dav*_*mac 9 java swing multithreading

我有一个Java程序,它在一个单独的(非EDT)线程上执行紧密循环.虽然我认为Swing UI仍然应该响应,但事实并非如此.下面的示例程序显示了问题:单击"试用我"按钮应该在大约一半时间后弹出一个对话框,并且应该可以通过单击其任何响应立即关闭该对话框.相反,对话框需要更长的时间才能显示,并且/或者在单击其中一个按钮后需要很长时间才能关闭.

  • Linux(两台具有不同发行版的不同计算机),在Windows上,在Raspberry Pi(仅限服务器VM)和Mac OS X上(由另一个SO用户报告)出现问题.
  • Java版本1.8.0_65和1.8.0_72(两者都试过)
  • i7处理器有很多核心.EDT应该有足够的备用处理能力.

有没有人知道为什么EDT处理被延迟,即使只有一个繁忙的线程?

(请注意,尽管Thread.sleep呼叫的各种建议是导致问题的原因,但事实并非如此.它可以被移除,问题仍然可以重现,尽管它表现得稍微不那么频繁并且通常表现出上述第二种行为 - 即非- 响应JOptionPane对话而不是延迟对话框出现.此外,没有理由睡眠调用应该屈服于另一个线程,因为如上所述的备用处理器核心 ; EDT可以在调用后继续在另一个核心上运行sleep).

import java.awt.EventQueue;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;

public class MFrame extends JFrame
{
    public static void main(String[] args)
    {
        EventQueue.invokeLater(() -> {
            new MFrame();
        });
    }

    public MFrame()
    {
        JButton tryme = new JButton("Try me!");

        tryme.addActionListener((e) -> {
            Thread t = new Thread(() -> {
                int a = 4;
                for (int i = 0; i < 100000; i++) {
                    for (int j = 0; j < 100000; j++) {
                        a *= (i + j);
                        a += 7;
                    }
                }
                System.out.println("a = " + a);
            });

            t.start();

            // Sleep to give the other thread a chance to get going.
            // (Included because it provokes the problem more reliably,
            // but not necessary; issue still occurs without sleep call).
            try {
                Thread.sleep(500);
            }
            catch (InterruptedException ie) {
                ie.printStackTrace();
            }

            // Now display a dialog
            JOptionPane.showConfirmDialog(null, "You should see this immediately");
        });

        getContentPane().add(tryme);

        pack();
        setVisible(true);
    }
}
Run Code Online (Sandbox Code Playgroud)

更新:问题仅发生在服务器VM上(但请参阅进一步更新).指定客户端VM(-clientjava可执行文件的命令行参数)似乎可以抑制一台计算机上的问题(更新2),而不是另一台计算机上的问题.

更新3:单击按钮后,我看到Java进程的处理器使用率为200%,这意味着有2个处理器核心已满载.这对我来说根本没有意义.

更新4:也发生在Windows上.

更新5:使用调试器(Eclipse)证明是有问题的; 调试器似乎无法阻止线程.这是非常不寻常的,我怀疑VM中存在某种活锁或竞争条件,因此我向Oracle提交了一个错误(评论ID JI-9029194).

更新6:在OpenJDK错误数据库中找到了我的错误报告.(我没有被告知它已被接受,我不得不搜索它).那里的讨论最有趣,并且已经阐明了可能导致这个问题的原因.

mKo*_*bel 1

  • 默认情况下,所有内容都从 EDT 开始(在本例中为 ),并在执行所有代码(包括 )时ActionListener锁定结束,并假设您丢失了所有事件(包括)。绘画,在这段代码的整个时间内,所有事件都在最后一次性绘制 Thread.sleepThread.sleep

  • 此代码丢失了 autoclose JOptionPane,永远不会绘制到屏幕上(模拟如何轻松地Thread.sleep在 Swing 中终止绘制)

  • Swing GUI 对鼠标或按键事件不负责,不可能终止此应用程序,这仅可以从Runnable#ThreadSwingWorker开始,即指定启动新的另一个线程 ( Workers Thread),在内部的任何事情期间Runnable#Thread并且SwingWorker任务可取消(或者通过使用Runnable#Threadis可以暂停、修改...)

  • 这与多线程无关,也不与其他核心共享资源,在 Win10 中,所有核心都向我展示,按比例共享增量

(稍微修改过的)代码的输出(基于您的 SSCCE / MCVE)

run:
test started at - 16:41:13
Thread started at - 16:41:15
to test EDT before JOptionPane - true at 16:41:16
before JOptionPane at - 16:41:16
Thread ended at - 16:41:29
a = 1838603747
isEventDispatchThread()false
after JOptionPane at - 16:41:29
Thread started at - 16:41:34
to test EDT before JOptionPane - true at 16:41:34
before JOptionPane at - 16:41:34
Thread ended at - 16:41:47
a = 1838603747
isEventDispatchThread()false
after JOptionPane at - 16:41:47
BUILD SUCCESSFUL (total time: 38 seconds)
Run Code Online (Sandbox Code Playgroud)

再次 autocloseJOptionPane永远不会被绘制到屏幕上(测试过 win10-64b、i7、Java8),可能到 Java 1.6.022 一切都会正确绘制(AFAIK 对 edt 的最后修复,从这次开始SwingWorker没有错误)

import java.awt.Component;
import java.awt.EventQueue;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.swing.AbstractAction;
import javax.swing.Action;

import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import javax.swing.Timer;

public class MFrame extends JFrame {

    public static void main(String[] args) {
        EventQueue.invokeLater(() -> {
            new MFrame();
        });
    }

    public MFrame() {
        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
        System.out.println("test started at - " + sdf.format(getCurrDate().getTime()));
        //http://stackoverflow.com/a/18107432/714968
        Action showOptionPane = new AbstractAction("show me pane!") {
            private static final long serialVersionUID = 1L;

            @Override
            public void actionPerformed(ActionEvent e) {
                createCloseTimer(3).start();
                System.out.println("before JOptionPane at - "
                        + sdf.format(getCurrDate().getTime()));
                JOptionPane.showMessageDialog((Component) e.getSource(), "nothing to do!");
            }

            private Timer createCloseTimer(int seconds) {
                ActionListener close = new ActionListener() {

                    @Override
                    public void actionPerformed(ActionEvent e) {
                        Window[] windows = Window.getWindows();
                        for (Window window : windows) {
                            if (window instanceof JDialog) {
                                JDialog dialog = (JDialog) window;
                                if (dialog.getContentPane().getComponentCount() == 1
                                        && dialog.getContentPane().getComponent(0) instanceof JOptionPane) {
                                    dialog.dispose();
                                    System.out.println("after JOptionPane at - "
                                            + sdf.format(getCurrDate().getTime()));
                                }
                            }
                        }
                    }
                };
                Timer t = new Timer(seconds * 1000, close);
                t.setRepeats(false);
                return t;
            }
        };
        JButton tryme = new JButton("Try me!");
        tryme.addActionListener((e) -> {
            System.out.println("Thread started at - "
                    + sdf.format(getCurrDate().getTime()));
            Thread t = new Thread(() -> {
                int a = 4;
                for (int i = 0; i < 100000; i++) {
                    for (int j = 0; j < 100000; j++) {
                        a *= (i + j);
                        a += 7;
                    }
                }
                System.out.println("Thread ended at - "
                        + sdf.format(getCurrDate().getTime()));
                System.out.println("a = " + a);
                System.out.println("isEventDispatchThread()" + 
                        SwingUtilities.isEventDispatchThread());
            });
            t.start();
            // Sleep to give the other thread a chance to get going:
            try {
                Thread.sleep(500);
            } catch (InterruptedException ie) {
                ie.printStackTrace();
            }
            // Now display a dialog
            System.out.println("to test EDT before JOptionPane - "
                    + SwingUtilities.isEventDispatchThread()
                    + " at " + sdf.format(getCurrDate().getTime()));
            showOptionPane.actionPerformed(e);
        });
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        add(tryme);
        pack();
        setLocation(150, 150);
        setVisible(true);
    }

    private Date getCurrDate() {
        java.util.Date date = new java.util.Date();
        return date;
    }
}
Run Code Online (Sandbox Code Playgroud)

注意也必须用Runnable#Threadand进行测试SwingWorker


归档时间:

查看次数:

947 次

最近记录:

10 年,4 月 前