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

我有一个Java程序,在一个单独的(非EDT)线程上执行一个严格的循环。 尽pipe我认为Swing UI仍然可以响应,但事实并非如此。 下面的示例程序显示了这个问题:单击“尝试我”button应该会在半秒钟后或多或less地popup一个对话框,并且应该可以通过单击任何响应来立即closures该对话框。 相反,对话框需要更长的时间才能显示,并且/或者点击其中一个button需要很长时间才能closures。

  • 问题发生在Linux(两个不同的发行版),Windows,Raspberry Pi(仅限服务器VM)和Mac OS X(由另一个SO用户报告)上。
  • Java版本1.8.0_65和1.8.0_72(都尝试过)
  • i7处理器拥有多个内核。 EDT应该有足够的备用处理能力。

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

(请注意,虽然Thread.sleep调用的各种build议是造成问题的原因,但它不是,它可以被删除,问题仍然可以被复制,尽pipe它显示的频率稍低,并且通常performance出上述第二种行为- 即没有响应的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); } } 

更新:问题只发生在服务器虚拟机( 但看到进一步的更新 )。 指定客户端VM(对java可执行文件的客户端命令行参数)似乎在一台机器上抑制了问题( 更新2,而不是另一台

更新3:点击button后,Java进程看到200%的处理器使用率,意味着有2个处理器内核已经完全加载。 这对我来说根本没有意义。

更新4:也发生在Windows上。

更新5:使用debugging器(Eclipse)certificate有问题; debugging器似乎无法停止线程。 这是非常不寻常的,我怀疑虚拟机中存在某种活锁或竞争状态,所以我提交了Oracle的一个错误(评论ID JI-9029194)。

更新6:我在OpenJDK错误数据库中发现了我的错误报告 。 (我没有被通知它已被接受,我不得不寻找它)。 这里的讨论最有意思,并且已经阐明了这个问题的原因。

我看到了与Mac OS X相同的效果。虽然您的示例已正确同步,但是您看到的平台/ JVM可变性可能是由于计划中的线程变幻莫测,导致了饥饿。 将Thread.yield()添加到t的外部循环中可缓解问题,如下所示。 除了这个例子的人为特征之外, Thread.yield()这样的提示通常是不需要的。 在任何情况下,考虑SwingWorker , 这里显示执行一个类似的严格循环出于演示的目的。

我不相信Thread.yield()在这种情况下根本就不需要被调用,尽pipe这个testing用例是人为的。

正确; 产生简单的暴露底层的问题: t 饿死事件调度线程。 请注意,即使没有Thread.yield() ,GUI在下面的示例中也会立即更新。 正如在这个相关的Q&A中所讨论的那样,您可以尝试降低线程的优先级。 或者,在单独的JVM中运行t ,如在此处使用ProcessBuilder所build议的,也可以在SwingWorker的后台运行,如此处所示。

为什么

所有支持的平台都build立在单线程graphics库上。 阻止,扼杀或饱和pipe理事件调度线程是相当容易的。 非平凡的后台任务通常隐含地产生,就像发布中间结果,阻塞I / O或等待工作队列一样。 不做的任务可能必须明确地产生。

图片

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

我的观察:

  • 用swingworkerreplace线程:

    • 没有不同
  • 用swingworkerreplace线程,并在第一个for循环内部做一些工作:

    • 得到了不冻结的预期结果,从这里开始一帆风顺

有了这个代码,就可以看到预期的行为:

 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的原始代码:

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

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

我在一个半function的机器上运行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) 

我的观察结果是什么意思?

除了所有的东西之外,没有什么东西不会丢失,而且有人可能已经优化了编译器太多,这使得它以某种方式阻止了UI线程。 老实说,我不知道这是什么意思,但我相信有人会弄清楚

  • 默认情况下,所有从Thread.sleeplocking的EDT(本例中为ActionListener内部)开始的所有代码都被执行,包括Thread.sleep ,并假设您丢失了所有事件。 绘画,在整个过程中encryption这个代码,所有的事件都画在最后一次

  • 这段代码丢失了自动闭合的JOptionPane ,从来没有被绘制到屏幕上(模拟如何安装Thread.sleep杀死Swing中的绘画)

  • Swing GUI对于鼠标或键盘事件是不负责任的,不可能终止这个应用程序,这可能仅仅是从Runnable#ThreadSwingWorker ,那是指定开始新的,另一个线程( Workers Thread ),在Runnable#ThreadSwingWorker是任务可取消(或通过使用Runnable#Thread有可能暂停,修改…)

  • 这不是关于multithreading,也不是将资源共享给另一个核心,在Win10中,所有内核都显示给我,按比例分享增量

输出(less许修改)的代码(基于你的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) 

再次autoclose JOptionPane永远不会被绘制到屏幕上(testingwin10-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; } } 

注意也必须使用Runnable#ThreadSwingWorker进行testing

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

我试图尽量减less代码,以消除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标志来确认我的结果吗?

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

  1. 执行示例程序
  2. 点击“尝试我”button
  3. 点击任何button(是/否/取消)closures结果对话框

在Windows上

长时间观察第2步和第3步。

第3步 – >立即closures对话框。

在Linux上

步骤2到步骤3 – 不延迟。

步骤3 – >长时间closures对话框。