Псевдоблоки Swing при запуске Thread ИЛИ SwingWorker, который выполняет вычисления - PullRequest
2 голосов
/ 03 февраля 2011

У меня очень простое приложение с очень странным поведением.

По сути, это пример SwingWorker, но когда я нажимаю кнопку, графический интерфейс ведет себя так же, как блокируется EDT. Я могу запустить два одновременно, и они работают параллельно (имеют примерно одинаковое время выполнения), но меню все еще зависает, пока они работают. Точно такое же поведение происходит, когда я использую поток с работоспособным. Также интересно то, что графический интерфейс ведет себя правильно, если циклы заменены на Thread.sleep.

Есть идеи?

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

Ответы [ 2 ]

2 голосов
/ 03 февраля 2011

Проблема в реализации потоков в виртуальной машине. В спецификации не указано, как это сделать. Предполагается, что потоки Java сопоставляются с собственными потоками Windows, а затем используют планировщик Windows для совместного использования временных интервалов. Неясно, действительно ли это происходит, и вся официальная документация поддерживает только информацию о потоках, запущенную в Solaris.

Я считаю, что основная проблема заключается в деталях реализации для вытеснения потоков. Вероятно, это вызвано некоторой комбинацией оптимизации кода при компиляции и управлении вытеснением между JVM и собственной ОС. JVM могут использовать вызовы методов в качестве точек для вытеснения потоков, и я думаю, что частью проблемы здесь являются два цикла, которые вы вызываете один поверх другого. Если вы разберетесь с ними с помощью вызова функции, он будет работать намного лучше на моей машине. Я использую 1.6.0_23 64-битную серверную виртуальную машину в Windows 7.

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() каждый раз, когда вы запускаете внутренний цикл. Это гарантирует, что скомпилированный код дает планировщику возможность каждой итерации выгружать поток.

0 голосов
/ 03 февраля 2011

Я думаю, проблема в том, что этот код может использовать 100% CPU так легко.Если у вас есть один поток на каждое ядро, больше ничего не остается.

...