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

时间:2016-02-02 13:05:35

标签: java multithreading swing

我有一个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对话框而不是延迟对话框外观。此外,没有理由睡眠调用应该屈服于另一个线程,因为有备用处理器核心如上所述;在致电sleep之后,EDT可以继续在另一个核心上运行。

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);
    }
}

更新:仅在服务器VM(,但请参阅进一步更新)时才会出现此问题。指定客户端VM(java可执行文件的-client命令行参数)似乎可以在一台计算机上解决问题(更新2 ,而不是另一台

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

更新4:也会在Windows上发生。

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

更新6:我找到了my bug report in the OpenJDK bug database。 (我没有被告知它已被接受,我不得不搜索它)。那里的讨论最有趣,并且已经解释了可能导致这个问题的原因。

5 个答案:

答案 0 :(得分:2)

我看到了与Mac OS X相同的效果。虽然您的示例已正确同步,但您看到的平台/ JVM可变性可能是由于线程如何安排变幻莫测,导致饥饿。将Thread.yield()添加到t中的外部循环可以缓解此问题,如下所示。除了示例的人为性质之外,通常需要像Thread.yield()这样的提示not。在任何情况下,请考虑SwingWorker,显示here执行类似的紧密循环以进行演示。

  

尽管测试用例存在人为性质,但我认为在这种情况下根本不需要调用Thread.yield()

正确;屈服只是暴露了潜在的问题:t starves事件派遣线程。请注意,即使没有Thread.yield(),GUI也会在下面的示例中立即更新。如本相关Q&A中所述,您可以尝试降低线程的优先级。或者,使用t按照建议here在单独的JVM中运行ProcessBuilderSwingWorker也可以在import java.awt.EventQueue; import javax.swing.JButton; import javax.swing.JFrame; public class MFrame extends JFrame { private static final int N = 100_000; private static final String TRY_ME = "Try me!"; private static final String WORKING = "Working…"; public static void main(String[] args) { EventQueue.invokeLater(new MFrame()::display); } private void display() { JButton tryme = new JButton(TRY_ME); tryme.addActionListener((e) -> { Thread t = new Thread(() -> { int a = 4; for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { a *= (i + j); a += 7; } Thread.yield(); } EventQueue.invokeLater(() -> { tryme.setText(TRY_ME); tryme.setEnabled(true); }); System.out.println("a = " + a); }); t.start(); tryme.setEnabled(false); tryme.setText(WORKING); }); add(tryme); pack(); setLocationRelativeTo(null); setDefaultCloseOperation(EXIT_ON_CLOSE); setVisible(true); } } 的后台运行,如here所示。

  

为什么

所有支持的平台都基于单线程图形库构建。阻塞,饿死或饱和管理事件派遣线程相当容易。非平凡的后台任务通常会隐式产生,就像发布中间结果,阻塞I / O或等待工作队列一样。 的任务可能必须明确产生。

image

<record id="action_liste_patients" model="ir.actions.act_window">
    <field name="name">Patients</field>
    <field name="res_model">hr.employee</field>
    <field name="view_mode">kanban,tree,form</field>
    <field name="domain">[('department_id.is_patients', '=', 'true')]</field>
    <field name="context">{'create_as_patient': True}</field>
</record>

答案 1 :(得分:2)

我的观察:

  • 用swingworker替换线程:

    • 没有区别
  • 用swingworker替换线程,并在第一个for循环中做一些工作:

    • 得到了ui没有结冰的预期结果,而且它从这里开始顺利航行

使用此代码,可以观察到预期的行为:

public class MFrame extends JFrame {
    public static void main(String[] args) {
        new MFrame();
    }

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

        tryme.addActionListener((e) -> {
            SwingWorker<Void, Void> longProcess = new SwingWorker<Void, Void>() {
                private StringBuilder sb = new StringBuilder();

                @Override
                protected Void doInBackground() throws Exception {
                    int a = 4;
                    for (int i = 0; i < 100000; i++) {
                        for (int j = 0; j < 100000; j++) {
                            a *= (i + j);
                            a += 7;
                        }
                        sb.append(a); // <-- this seems to be the key
                    }
                    System.out.println("a = " + a);
                    return null;
                }

                @Override
                protected void done() {
                    try {
                        get();
                        System.out.println(sb.toString());
                    } catch (InterruptedException | ExecutionException e1) {
                        e1.printStackTrace();
                    }
                }
            };

            longProcess.execute();

            // 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
            SwingUtilities.invokeLater(() -> JOptionPane.showConfirmDialog(this, "You should see this immediately"));
        });

        getContentPane().add(tryme);
        pack();
        setLocationRelativeTo(null);
        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        setVisible(true);
    }
}

使用OP的原始代码进行相同的观察:

  • 在第一个for循环中做一些其他的工作
    • 得到预期的结果

实施例

tryme.addActionListener((e) -> {

    Thread t = new Thread(() -> {
        StringBuilder sb = new StringBuilder();

        int a = 4;
        for (int i = 0; i < 100000; i++) {
            for (int j = 0; j < 100000; j++) {
                a *= (i + j);
                a += 7;
            }
            sb.append(a); // <-- again the magic seems to be on this line
        }
        System.out.println(sb.toString());
    });
    ...
});

我在一台半功能强大的机器上运行Ubuntu 14.04。

java version "1.8.0_72"
Java(TM) SE Runtime Environment (build 1.8.0_72-b15)
Java HotSpot(TM) 64-Bit Server VM (build 25.72-b15, mixed mode)

我的观察意味着什么?

除了all之外没有多少没有丢失,有些人可能已经过多地优化了编译器,这使得它以某种方式阻止了UI线程。老实说,我不确定这一切意味着什么,但我确定有人会弄清楚

答案 2 :(得分:1)

  • 默认情况下,从ActionListener开始的所有代码执行包括Thread.sleep后,所有从Thread.sleep内部JOptionPane开始的内容都会终止,并假设您丢失了所有活动包括绘画,在整个时间内完成这段代码,所有事件都在最后和一次绘制

  • 此代码丢失了自动关闭Thread.sleep,永远不会被绘制到屏幕上(模拟如何在Swing中轻松Runnable#Thread杀死绘画)

  • Swing GUI对鼠标或键事件不负责任,不能终止此应用程序,这可以从SwingWorkerWorkers Thread开始,这被指定为开始新的,Runnable#ThreadSwingWorker内的任何内容(Runnable#Thread)在任务可取消期间(或使用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) 可以暂停,修改......)

  • 这不是关于多线程,也不是要将资源管理器共享给另一个核心,在Win10中所有核心都显示我,按比例共享增量

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

JOptionPane

再次自动关闭SwingWorker永远不会被绘制到屏幕上(经过测试的win10-64b,i7,Java8),可能达到Java 1.6.022所有内容都将被绘制并且正确(AFAIK最后修复edt和来自这次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; } } 无错误地工作)

Runnable#Thread

注意必须使用SwingWorkerINSERT INTO dbo.rolls ( name, subject ) VALUES ( 'Jones', 'English'), ( 'Smith', 'Math'), ('Adams','English'), ('Adams', 'Math') GO ;WITH CTE AS ( SELECT subquery1.name, 'B' AS code FROM ( SELECT name,COUNT(name) AS cnt FROM rolls WHERE subject = 'English' OR subject = 'Math' GROUP BY name HAVING COUNT(name) > 1 ) AS subquery1 UNION SELECT subquery2.name, SUBSTRING(rolls.subject,1,1) AS code FROM ( SELECT name,COUNT(name) AS cnt FROM rolls WHERE subject = 'English' OR subject = 'Math' GROUP BY name HAVING COUNT(name) = 1 ) AS subquery2 INNER JOIN dbo.rolls ON rolls.name = subquery2.name ) SELECT * FROM CTE 进行测试

答案 3 :(得分:1)

这不是最终答案,但它更接近于理解问题。

我尝试使用sleepactionPerformed最小化代码以消除潜在的陷阱,我相信我已经这样做了,同时保持问题的完整性:

public class MFrame extends JFrame {

    public static void main(String[] args) {

        EventQueue.invokeLater(() -> new MFrame());
    }

    public MFrame() {

        Thread t = new Thread(() -> {
            int a = 4;
            for (int i = 0; i < 50000; i++) {
                for (int j = 0; j < 50000; j++) {
                    a *= (i + j);
                    a += 7;
                }
            }
//          System.out.println("c" + a);
        });

        System.out.println("a");
//      pack();
        t.start();
//      pack();
        System.out.println("b");
    }
}

在Win7,i7 2670QM,JDK 1.8.0_25上我得到以下结果:

只有第二个pack注释掉了:

a
b [pause]
c-1863004573

(预计即使没有同步,也会在打印b之前达到打印c,除非您使用的某些超级处理器可以更快地进行计算?)

只有第一个pack注释掉了:

a [pause]
c-1863004573
b

(不是预期的)

您可以使用-client标志确认我的结果吗?

答案 4 :(得分:1)

这似乎是一个问题。下面是观察Event Dispatcher Thread延迟处理,应该立即响应:

  1. 执行示例程序
  2. 点击&#34;试试我&#34;按钮
  3. 单击任意按钮(是/否/取消)以关闭生成的对话框
  4. 在Windows上

    在步骤2和步骤3之间进行长时间延迟观察。

    步骤3 - &gt;立即关闭对话框。

    在Linux上

    第2步到第3步 - 没有延迟。

    步骤3 - &gt;很长时间关闭对话框。