启动执行计算的线程或 SwingWorker 时的 Swing 伪块
我有一个非常简单的应用程序,但行为非常奇怪。
它本质上是 SwingWorker 示例,但当我按下按钮时,GUI 的行为就像 EDT 被阻止一样。我可以同时启动两个,它们并行运行(运行时间几乎相同),但菜单在它们运行时仍然冻结。当我使用带有可运行对象的线程时,会发生完全相同的行为。同样有趣的是,如果循环被 Thread.sleep 替换,GUI 会正常运行。
有什么想法吗?
public class DummyFrame extends JFrame {
public DummyFrame() {
JMenuBar bar = new JMenuBar();
JMenu menu = new JMenu("File");
menu.add(new JMenuItem("TEST1"));
menu.add(new JMenuItem("Test2"));
bar.add(menu);
setJMenuBar(bar);
JButton button = new JButton("FOOBAR");
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
final long start = System.currentTimeMillis();
SwingWorker<Void, Integer> testTask = new SwingWorker<Void, Integer>() {
@Override
protected Void doInBackground()
throws Exception {
int k = 0;
for (int i=0; i<200000; i++) {
for (int j=0; j<100000; j++) {
if (i==j && i%10000 == 0)
k++;
}
}
System.out.println(k+" "+(System.currentTimeMillis()-start));
return null;
}
};
testTask.execute();
}
});
getContentPane().add(button);
pack();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
DummyFrame f = new DummyFrame();
f.setVisible(true);
}
});
}
}
I have a very simple application with a very strange behaviour.
It's essentially the SwingWorker example, but when I press the button the GUI behaves just like the EDT is being blocked. I can fire off two simultaneously and they run in parallel (have near identical run times) but still the menu freezes up while they're running. The exact same behaviour happens when I use a Thread with a runnable. Also interesting is that the GUI behaves properly if the loops are replaced by a Thread.sleep.
Any ideas?
public class DummyFrame extends JFrame {
public DummyFrame() {
JMenuBar bar = new JMenuBar();
JMenu menu = new JMenu("File");
menu.add(new JMenuItem("TEST1"));
menu.add(new JMenuItem("Test2"));
bar.add(menu);
setJMenuBar(bar);
JButton button = new JButton("FOOBAR");
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
final long start = System.currentTimeMillis();
SwingWorker<Void, Integer> testTask = new SwingWorker<Void, Integer>() {
@Override
protected Void doInBackground()
throws Exception {
int k = 0;
for (int i=0; i<200000; i++) {
for (int j=0; j<100000; j++) {
if (i==j && i%10000 == 0)
k++;
}
}
System.out.println(k+" "+(System.currentTimeMillis()-start));
return null;
}
};
testTask.execute();
}
});
getContentPane().add(button);
pack();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
DummyFrame f = new DummyFrame();
f.setVisible(true);
}
});
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
问题出在虚拟机的线程实现上。该规范没有指定如何完成此操作。 Java 线程应该映射到本机 Windows 线程,然后使用 Windows 调度程序来共享时间片。目前尚不清楚这是否确实如此,并且所有官方文档仅支持在 Solaris 上运行的线程信息。
我认为主要问题来自于线程抢占的实现细节。这可能是由 JVM 和本机操作系统之间的编译代码优化和抢占控制的某种组合引起的。 JVM 可以使用方法调用作为抢占线程的点,我认为这里的部分问题在于您在另一个循环之上调用一个循环。如果你用函数调用来分解它们,它在我的机器上表现得更好。我在 Windows 7 上使用 1.6.0_23 64 位服务器虚拟机。
但是,即使在同时启动其中多个虚拟机后,也会出现问题。最好的解决方案是在每次启动内循环时添加对 Thread.yield() 的调用。这确保了编译后的代码为调度程序提供了每次迭代抢占线程的机会。
The problem is in the VM's implementation of threads. The specification does not specify how this is to be done. Java threads are supposed to map to native Windows threads and then use the Windows scheduler to share time slices. It's unclear if this is exactly going on, and all of the official documentation only supports thread information running on Solaris.
I believe the main problem comes from implementation details for preemption of threads. Probably this is being caused by some combination of code optimization on compiling and preemption control between the JVM and the native OS. JVM's can use method calls as points to preempt threads, and I think part of the problem here is the two loops that you're calling one on top of the other. If you break them up with a function call, it performs much better on my machine. I'm using 1.6.0_23 64-Bit Server VM on Windows 7.
Even this has problems after starting several of them at once, however. The best solution is to add a call to
Thread.yield()
each time you start the inner loop. This ensures that the compiled code gives the scheduler an opportunity each iteration to preempt the thread.我认为问题在于该代码可以如此轻松地使用 100% CPU。如果每个核心有一个线程运行该线程,则没有太多空间可以容纳其他任何东西。
I think the problem is just that that code can use 100% CPU so easily. If you have one thread per core running that, there isn't much room for anything else.