其他线程中的java忙循环延迟了EDT处理
我有一个Java程序,它在一个单独的(非EDT)线程上执行紧循环。尽管我认为Swing UI应该仍然具有响应性,但事实并非如此。下面的示例程序显示了这个问题:单击“Try me”按钮应该会在半秒钟后弹出一个对话框,并且应该可以通过单击对话框的任何响应立即关闭该对话框。相反,对话框需要更长的时间才能显示,和/或在单击其中一个按钮后需要很长时间才能关闭
- 问题发生在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(-client
命令行参数指定为java可执行文件)似乎可以在一台机器上抑制问题(update 2),但在另一台机器上却不能
更新3:单击按钮后,我看到Java进程使用了200%的处理器,这意味着有两个处理器内核已完全加载。这对我来说毫无意义
更新4:在Windows上也会出现
更新5:使用调试器(Eclipse)证明是有问题的;调试器似乎无法停止线程。这是非常不寻常的,我怀疑VM中存在某种活锁或竞争条件,所以我向Oracle提交了一个bug(查看ID JI-9029194)
更新6:我找到了my bug report in the OpenJDK bug database。(我没有被告知它已被接受,我不得不搜索它)。那里的讨论非常有趣,已经揭示了这个问题的原因
# 1 楼答案
我在Mac OS X上也看到了同样的效果。虽然您的示例已正确同步,但您看到的平台/JVM可变性很可能是由于线程调度方式的变化无常,导致了饥饿。将^{} 添加到} ,如{{a4}〉,为了演示目的,执行类似的紧循环。
t
中的外部循环可以缓解问题,如下所示。除了示例的人工性质之外,通常需要Thread.yield()
这样的提示。在任何情况下,考虑^{正确;屈服只会暴露出潜在的问题:
t
starves事件调度线程。请注意,在下面的示例中,GUI会迅速更新,即使没有Thread.yield()
。正如在相关的Q&A中所讨论的,您可以尝试降低线程的优先级。或者,在单独的JVM中运行t
,如建议的here使用ProcessBuilder
,也可以在SwingWorker
的后台运行,如图here所有受支持的平台都构建在单线程图形库上。很容易阻塞、饿死或使管理事件调度线程饱和。非平凡的后台任务通常会隐式地产生结果,比如发布中间结果、阻塞I/O或等待工作队列时。一项任务如果不是所做的,就必须明确地做出让步