Busy loop in other thread delays EDT processing

I see the same effect to Mac OS X. Although your example is correctly synchronized, the platform/JVM variability you see is likely due to vagaries in how threads are scheduled, resulting is starvation. Adding Thread.yield() to the outer loop in t mitigates the problem, as shown below. Except for the artificial nature of the example, a hint like Thread.yield() would not ordinarily be required. In any case, consider SwingWorker, shown here executing a similarly tight loop for demonstration purposes.

I do not believe that Thread.yield() should need to be called in this case at all, despite the artificial nature of the test case, however.

Correct; yielding simply exposes the underlying problem: t starves the event dispatch thread. Note that the GUI updates promptly in the example below, even without Thread.yield(). As discussed in this related Q&A, you can try lowering the thread’s priority. Alternatively, run t in a separate JVM, as suggested here using ProcessBuilder, which can also run in the background of a SwingWorker, as shown here.

but why?

All supported platforms are built on single-threaded graphics libraries. It’s fairly easy to block, starve or saturate the governing event dispatch thread. Non-trivial background tasks typically yield implicitly, as when publishing intermediate results, blocking for I/O or waiting on a work queue. A task that does not do may have to yield explicitly.

image

import java.awt.EventQueue;
import javax.swing.JButton;
import javax.swing.JFrame;

public class MFrame extends JFrame {

    private static final int N = 100_000;
    private static final String TRY_ME = "Try me!";
    private static final String WORKING = "Working…";

    public static void main(String[] args) {
        EventQueue.invokeLater(new MFrame()::display);
    }

    private void display() {
        JButton tryme = new JButton(TRY_ME);
        tryme.addActionListener((e) -> {
            Thread t = new Thread(() -> {
                int a = 4;
                for (int i = 0; i < N; i++) {
                    for (int j = 0; j < N; j++) {
                        a *= (i + j);
                        a += 7;
                    }
                    Thread.yield();
                }
                EventQueue.invokeLater(() -> {
                    tryme.setText(TRY_ME);
                    tryme.setEnabled(true);
                });
                System.out.println("a = " + a);
            });
            t.start();
            tryme.setEnabled(false);
            tryme.setText(WORKING);
        });
        add(tryme);
        pack();
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setVisible(true);
    }
}

Leave a Comment