2

非常に奇妙な動作をする非常に単純なアプリケーションがあります。

これは基本的に SwingWorker の例ですが、ボタンを押すと、EDT がブロックされているかのように GUI が動作します。2 つを同時に起動でき、それらは並行して実行されます (実行時間はほぼ同じです) が、実行中にメニューがフリーズします。ランナブルでスレッドを使用すると、まったく同じ動作が発生します。また興味深いのは、ループが 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

問題は、VM のスレッドの実装にあります。仕様では、これを行う方法を指定していません。Java スレッドは、ネイティブ Windows スレッドにマップされ、Windows スケジューラを使用してタイム スライスを共有することになっています。これが正確に進行しているかどうかは不明であり、すべての公式ドキュメントは Solaris で実行されているスレッド情報のみをサポートしています。

主な問題は、スレッドのプリエンプションの実装の詳細にあると思います。おそらくこれは、コンパイル時のコードの最適化と、JVM とネイティブ OS 間のプリエンプション制御の組み合わせによって引き起こされています。JVMはメソッド呼び出しをポイントとして使用してスレッドをプリエンプトできますが、ここでの問題の一部は、2つのループを重ねて呼び出していることにあると思います。関数呼び出しでそれらを分割すると、私のマシンでのパフォーマンスが大幅に向上します。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 を簡単に使用できることだと思います。それを実行しているコアごとに 1 つのスレッドがある場合、それ以外の余地はあまりありません。

于 2011-02-03T01:11:20.760 に答える