javamultithreadingevent-dispatch-thread

Is there a way to set up two or more event dispatch threads (EDT)?


Is Java capable of creating more than one EDT at a time?

I'm experimenting with setting up EDT and how it works in updating the content of a "heavy duty" panel with potentially a dozen of panels embedded inside and with hundreds of components altogether. Currently I have

        public void run() {
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    panel.update();
                }
            });
        }

I've looked at the following posts:

Measuring "busyness" of the event dispatching thread

How does the event dispatch thread work?

Java Event-Dispatching Thread explanation

http://en.wiki2.org/wiki/Event_dispatching_thread

and so forth.

I sort of understand that if there are, say a dozen of events, that an single EDT has to handle, Java already has an internal scheduling mechanism to group/prioritize these events.

According to http://docs.oracle.com/javase/tutorial/uiswing/concurrency/dispatch.html

"This is necessary because most Swing object methods are not "thread safe": invoking them from multiple threads risks thread interference or memory consistency errors."

So what if I create a 2nd EDT with new Thread(new Runnable() { ... }.start() below?

Will java automatically merge the two EDTs back to one for fear of thread safety?

       new Thread(new Runnable() {
        public void run() {
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    panel.update();
                }
            });
        }
    }).start();

Solution

  • The following applies in case one uses the Sun toolkit. I am not sure about other Java implementations (have not tested it).

    One can have multiple EDT threads provided the toolkit is initialized from multiple threads from separate thread groups.

    PoC code:

    import java.awt.BorderLayout;
    import java.awt.Dimension;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    
    import javax.swing.JButton;
    import javax.swing.JFrame;
    import javax.swing.JOptionPane;
    import javax.swing.JTextArea;
    import javax.swing.SwingUtilities;
    
    public class Test {
        public static void main(String[] argv) {
            for (int i = 0; i < 5; i++) {
                // the separate thread group is needed at least for the Sun toolkit
                ThreadGroup tg = new ThreadGroup("Test Group " + i);
                Thread t = new Thread(tg, new Runnable() {
                    @Override
                    public void run() {
                        startApp();
                    }
                }, "Test " + i);
                t.start();
            }
        }
        
        private static void startApp() {
            sun.awt.SunToolkit.createNewAppContext();
            final JFrame frm = new JFrame(Thread.currentThread().getName()) {
                @Override
                public void setVisible(boolean b) {
                    super.setVisible(b);
                    System.out.println("Closed");
                    if (!b) dispose();
                }
            };
            final JTextArea ta = new JTextArea();
            frm.add(ta);
            JButton btn = new JButton("Dialog");
            // Showing a modal dialog will block only this frame
            btn.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    JOptionPane.showMessageDialog(frm, "Test message from " + frm.getTitle());
                }
            });
            frm.add(btn, BorderLayout.SOUTH);
            frm.setPreferredSize(new Dimension(300, 300));
            frm.pack();
            frm.show();
            Thread t = new Thread(new Runnable() {
                int i = 0;
                @Override
                public void run() {
                    try {
                        while (true) {
                            i++;
                            if (!frm.isVisible()) break;
                            SwingUtilities.invokeAndWait(new Runnable() {
                                @Override
                                public void run() {
                                    try {
                                        ta.getDocument().insertString(0, "Test " + i + " " +
                                            Thread.currentThread().hashCode()  + // This is to show that we are actually on a different thread as invokeAndWait is static and one may suspect a different behaviour
                                            "\n", null);
                                    } catch (Throwable t) {
                                        t.printStackTrace();
                                    }
                                }
                            });
                            Thread.sleep(1000 + (int)(Math.random() * 500));
                        }
                        Thread.sleep(4000); // This is just to show that the deamon thread might not have ended before the toolkit calls System.exit
                    } catch (Throwable t) {
                        t.printStackTrace();
                    }
                    System.out.println("Thread " + Thread.currentThread().getName() + " exit");
                }
            });
            t.setDaemon(true);
            t.start();
        }
    }
    

    The above code demonstrates opening 5 frames on separate EDTs. When each frame is closed and disposed the respective EDT ends. When all the frames are closed and disposed the app will exit (automatically).

    Notes:

    I use the above approach in order to measure the height of a wrap enabled JTextArea that is populated with a multitude of different texts that might take some time and during that time I do not want to block the main UI. The result (the measured heights) is used on the main UI.

    Tested with Java 1.6.0_25 and 1.8.0_60.