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

My observations:

  • Replace thread with swingworker:

    • No difference
  • Replace thread with swingworker and do some work inside the first for loop:

    • Got the expected results of the ui not freezing and the it was just smooth sailing from here on

With this code, the expected behaviour is observed:

public class MFrame extends JFrame {
    public static void main(String[] args) {
        new MFrame();
    }

    public MFrame() {
        JButton tryme = new JButton("Try me!");

        tryme.addActionListener((e) -> {
            SwingWorker<Void, Void> longProcess = new SwingWorker<Void, Void>() {
                private StringBuilder sb = new StringBuilder();

                @Override
                protected Void doInBackground() throws Exception {
                    int a = 4;
                    for (int i = 0; i < 100000; i++) {
                        for (int j = 0; j < 100000; j++) {
                            a *= (i + j);
                            a += 7;
                        }
                        sb.append(a); // <-- this seems to be the key
                    }
                    System.out.println("a = " + a);
                    return null;
                }

                @Override
                protected void done() {
                    try {
                        get();
                        System.out.println(sb.toString());
                    } catch (InterruptedException | ExecutionException e1) {
                        e1.printStackTrace();
                    }
                }
            };

            longProcess.execute();

            // Sleep to give the other thread a chance to get going.
            // (Included because it provokes the problem more reliably,
            // but not necessary; issue still occurs without sleep call).
            try {
                Thread.sleep(500);
            }
            catch (InterruptedException ie) {
                ie.printStackTrace();
            }

            // Now display a dialog
            SwingUtilities.invokeLater(() -> JOptionPane.showConfirmDialog(this, "You should see this immediately"));
        });

        getContentPane().add(tryme);
        pack();
        setLocationRelativeTo(null);
        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        setVisible(true);
    }
}

The same observation is made by using OP's original code:

  • Do some other work inside the first for loop
    • Got expected results

Example

tryme.addActionListener((e) -> {

    Thread t = new Thread(() -> {
        StringBuilder sb = new StringBuilder();

        int a = 4;
        for (int i = 0; i < 100000; i++) {
            for (int j = 0; j < 100000; j++) {
                a *= (i + j);
                a += 7;
            }
            sb.append(a); // <-- again the magic seems to be on this line
        }
        System.out.println(sb.toString());
    });
    ...
});

I am running Ubuntu 14.04 on a semi powerful machine.

java version "1.8.0_72"
Java(TM) SE Runtime Environment (build 1.8.0_72-b15)
Java HotSpot(TM) 64-Bit Server VM (build 25.72-b15, mixed mode)

What do my observations mean?

Not much other than all is not lost and someone might have optimized the compiler a bit too much which makes it somehow block the UI thread. Honestly I'm not sure what it all means but I'm sure someone will figure it out


  • by default everything started from EDT (in this case inside the ActionListener) locked by Thread.sleep ended when all code is executed including Thread.sleep, with assumtion that you are lost all events incl. painting, during whole time cunsumed this code, all events are painted at the end and in one time

  • this code lost autoclose JOptionPane, never is painted to the screen (simulation of how to confortly Thread.sleep kills painting in Swing)

  • Swing GUI is unresponsible to mouse or key event, isn't possible to terminating this aplication, this is possible just from Runnable#Thread and SwingWorker, thats are designated to start new, another thread (Workers Thread), during anything inside Runnable#Thread and SwingWorker is task cancelable (or by using Runnable#Thread is there possible to pause, modify...)

  • this isn't about multithreading, nor about to share resourcer to another core(s), in Win10 all cores showing me, sharing the increment proportionally

output from (little bit modified) code (based on your SSCCE / MCVE)

run:
test started at - 16:41:13
Thread started at - 16:41:15
to test EDT before JOptionPane - true at 16:41:16
before JOptionPane at - 16:41:16
Thread ended at - 16:41:29
a = 1838603747
isEventDispatchThread()false
after JOptionPane at - 16:41:29
Thread started at - 16:41:34
to test EDT before JOptionPane - true at 16:41:34
before JOptionPane at - 16:41:34
Thread ended at - 16:41:47
a = 1838603747
isEventDispatchThread()false
after JOptionPane at - 16:41:47
BUILD SUCCESSFUL (total time: 38 seconds)

again autoclose JOptionPane never will be painted to the screen (tested win10-64b, i7, Java8), probably up to Java 1.6.022 everything will be painted and correctly (AFAIK the last fix to edt and from this time SwingWorker works without bugs)

import java.awt.Component;
import java.awt.EventQueue;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.swing.AbstractAction;
import javax.swing.Action;

import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import javax.swing.Timer;

public class MFrame extends JFrame {

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

    public MFrame() {
        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
        System.out.println("test started at - " + sdf.format(getCurrDate().getTime()));
        //http://stackoverflow.com/a/18107432/714968
        Action showOptionPane = new AbstractAction("show me pane!") {
            private static final long serialVersionUID = 1L;

            @Override
            public void actionPerformed(ActionEvent e) {
                createCloseTimer(3).start();
                System.out.println("before JOptionPane at - "
                        + sdf.format(getCurrDate().getTime()));
                JOptionPane.showMessageDialog((Component) e.getSource(), "nothing to do!");
            }

            private Timer createCloseTimer(int seconds) {
                ActionListener close = new ActionListener() {

                    @Override
                    public void actionPerformed(ActionEvent e) {
                        Window[] windows = Window.getWindows();
                        for (Window window : windows) {
                            if (window instanceof JDialog) {
                                JDialog dialog = (JDialog) window;
                                if (dialog.getContentPane().getComponentCount() == 1
                                        && dialog.getContentPane().getComponent(0) instanceof JOptionPane) {
                                    dialog.dispose();
                                    System.out.println("after JOptionPane at - "
                                            + sdf.format(getCurrDate().getTime()));
                                }
                            }
                        }
                    }
                };
                Timer t = new Timer(seconds * 1000, close);
                t.setRepeats(false);
                return t;
            }
        };
        JButton tryme = new JButton("Try me!");
        tryme.addActionListener((e) -> {
            System.out.println("Thread started at - "
                    + sdf.format(getCurrDate().getTime()));
            Thread t = new Thread(() -> {
                int a = 4;
                for (int i = 0; i < 100000; i++) {
                    for (int j = 0; j < 100000; j++) {
                        a *= (i + j);
                        a += 7;
                    }
                }
                System.out.println("Thread ended at - "
                        + sdf.format(getCurrDate().getTime()));
                System.out.println("a = " + a);
                System.out.println("isEventDispatchThread()" + 
                        SwingUtilities.isEventDispatchThread());
            });
            t.start();
            // Sleep to give the other thread a chance to get going:
            try {
                Thread.sleep(500);
            } catch (InterruptedException ie) {
                ie.printStackTrace();
            }
            // Now display a dialog
            System.out.println("to test EDT before JOptionPane - "
                    + SwingUtilities.isEventDispatchThread()
                    + " at " + sdf.format(getCurrDate().getTime()));
            showOptionPane.actionPerformed(e);
        });
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        add(tryme);
        pack();
        setLocation(150, 150);
        setVisible(true);
    }

    private Date getCurrDate() {
        java.util.Date date = new java.util.Date();
        return date;
    }
}

note have to test with Runnable#Thread and SwingWorker too