2

我有一个非常简单的应用程序,它的行为非常奇怪。

它本质上是 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);     
                        }
                });
        }
}
4

2 回答 2

2

问题在于虚拟机的线程实现。规范没有具体说明如何做到这一点。Java 线程应该映射到本地 Windows 线程,然后使用 Windows 调度程序来共享时间片。不清楚这是否真的发生了,所有的官方文档都只支持在 Solaris 上运行的线程信息。

我相信主要问题来自线程抢占的实现细节。这可能是由 JVM 和本机操作系统之间的编译和抢占控制的代码优化组合引起的。JVM 可以使用方法调用作为抢占线程的点,我认为这里的部分问题是您在另一个之上调用的两个循环。如果你用一个函数调用来分解它们,它在我的机器上表现得更好。我在 Windows 7 上使用 1.6.0_23 64 位服务器 VM。

SwingWorker<Void, Integer> testTask = new SwingWorker<Void, Integer>() {

    private int k;

    private void inc() {
        this.k++;
    }

    private void innerLoop(int i) {
        for (int j=0; j<100000; j++) {
            if (i==j && i%10000 == 0)
                this.inc();
        }
    }

    @Override
    protected Void doInBackground()
    throws Exception {
        System.out.println("Started");
        for (int i=0; i<200000; i++) {
            this.innerLoop(i);
        }
        System.out.println(k+" "+(System.currentTimeMillis()-start));
        return null;
    }
};

然而,在一次启动其中几个之后,即使这样也有问题。Thread.yield()最好的解决方案是在每次启动内部循环时添加一个调用。这确保了编译后的代码在每次迭代时都为调度程序提供了抢占线程的机会。

于 2011-02-03T02:23:35.517 回答
0

我认为问题在于该代码可以如此轻松地使用 100% CPU。如果每个核心有一个线程运行它,那么其他任何东西都没有太多空间。

于 2011-02-03T01:11:20.760 回答