1

Using the synchronized keyword on a method allows only one thread at a time to execute that method, but the EDT can process multiple "events" that will be running in that method concurrently. See sample code below for a demonstration. When you click the test button, the output is:

0 before dialog, EDT=true
1 before dialog, EDT=true
(click OK button for 1 here)
1 after dialog, EDT=true
(click OK button for 0 here)
0 after dialog, EDT=true

What I'm looking for is a way to only allow one EDT event to be active in the test() method at a time, so that the output would be

0 before dialog, EDT=true
(click OK button for 0 here)
0 after dialog, EDT=true
1 before dialog, EDT=true
(click OK button for 1 here)
1 after dialog, EDT=true

Seems like someone must have solved this problem before. I think it would be possible to write some kind of locking object to use at the beginning of the method, or to wrap the method, but being lazy, would rather not reinvent the wheel.

My test case:

package test1;

import javax.swing.JDialog;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;

public class EDTSyncTest extends javax.swing.JFrame {
  private static final Object locker = new Object();

  private int counter;

  public EDTSyncTest() {
    initComponents();
  }

  private synchronized void test() {
    int l_id = counter++;
    logit("" + l_id + " before dialog, EDT=" + SwingUtilities.isEventDispatchThread());

    JOptionPane l_pane = new JOptionPane("test id " + l_id);
    JDialog l_diag = l_pane.createDialog(this, "test");
    l_diag.setModal(true);
    l_diag.setVisible(true);

    logit("" + l_id + " after dialog, EDT=" + SwingUtilities.isEventDispatchThread());
  }

  private void startTest() {
     new Delayer().execute();
     test();
  }

  private static void logit(String a_msg) {
    System.out.println(a_msg);
  }

  private class Delayer extends SwingWorker<Object, Object> {
    @Override
    protected Object doInBackground() throws Exception {
      Thread.sleep(2000);
      return null;
    }
    @Override
    protected void done() {
      test();
    }
  }

  private void initComponents() {

    jButton1 = new javax.swing.JButton();

    setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
    getContentPane().setLayout(new java.awt.FlowLayout());

    jButton1.setText("Test");
    jButton1.setName("jButton1"); // NOI18N
    jButton1.addActionListener(new java.awt.event.ActionListener() {
      public void actionPerformed(java.awt.event.ActionEvent evt) {
        jButton1ActionPerformed(evt);
      }
    });
    getContentPane().add(jButton1);

    pack();
  }

  private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {                                         
    startTest();
  }                                        

  public static void main(String args[]) {

    java.awt.EventQueue.invokeLater(new Runnable() {
      public void run() {
        new EDTSyncTest().setVisible(true);
      }
    });
  }
  protected javax.swing.JButton jButton1;
}
Mitch
  • 989
  • 1
  • 9
  • 25
  • You are solving wrong problem. You should not do long computation in EDT – Jayan May 28 '13 at 23:33
  • @Jayan This is a more involved question (there is not necessarily a long computation in the EDT, here a background worker shows that it's not the focus) - there be magic in modal dialogs. If the modal dialogs are removed, the output order should be as expected. – user2246674 May 28 '13 at 23:37
  • 2
    @user2246674 You're right about the event loop. The `test()` method isn't being called concurrently, it's just being called recursively. – millimoose May 28 '13 at 23:44
  • @millimoose Ohh! That makes perfect sense then. Sneaky, sneaky. – user2246674 May 28 '13 at 23:45
  • @ Mitch : http://stackoverflow.com/questions/2829364/java-difference-between-swingworker-and-swingutilities-invokelater – Jayan May 28 '13 at 23:58

2 Answers2

5

Okay, well, here is my shot at an answer, guided by support from millimoose :D

Let's start with these observations:

  • There is only one EDT thread
  • A thread only has one execution context
  • A modal dialog blocks current execution context
  • SwingWorker.done is always invoked on the EDT

Thus, a modal dialog cannot block swing events or it would cause the entire UI to become unresponsive; then, how does it block without blocking swing events?

A modal dialog runs its own event dispatch loop. Then, the code execution call graph (which runs entirely on the EDT thread after completion of the SwingWorker) looks like this:

-> done (process FIRST done)
  -> 0/before
    -> modal dialog event loop (process NEXT done)
      -> 1/before
        -> modal dialog event loop (TOP DIALOG)
        <- OK PRESSED
      <- 1/after
    <- OK PRESSED
  <- 0/after 
<- back to normal EDT event loop

Thus, the modal dialogs still process swing events while they run, but in a "recursive" manner.

user2246674
  • 7,621
  • 25
  • 28
  • Now it all finally makes sense. I've been wondering for a while how the EDT could run a method concurrently. The fact that EDT is calling the method recursively clears up a lot of mystery for me. The example above was contrived, but it mirrors a problem I have in a real application. For now, I will work on the problem method to remove any blocking calls (e.g. JOptionPane) so that there will be no occasion for EDT to call it recursively. – Mitch May 29 '13 at 14:44
1

I have just figure out myself, but as @millimoose commented:

The test() method isn't being called concurrently, it's just being called recursively.

Modify your code a little and you'll see:

logit("" + l_id + " before dialog, EDT="
    + SwingUtilities.isEventDispatchThread());
new Throwable().printStackTrace(System.out);

Output:

0 before dialog, EDT=true
java.lang.Throwable
    at EDTSyncTest.test(EDTSyncTest.java:22)
    at EDTSyncTest.startTest(EDTSyncTest.java:35)
    at EDTSyncTest.jButton1ActionPerformed(EDTSyncTest.java:75)
    at EDTSyncTest.access$1(EDTSyncTest.java:74)
    at EDTSyncTest$1.actionPerformed(EDTSyncTest.java:66)
    at javax.swing.AbstractButton.fireActionPerformed(Unknown Source)
    /* ... */
    at java.awt.EventDispatchThread.run(Unknown Source)
1 before dialog, EDT=true
java.lang.Throwable
    at EDTSyncTest.test(EDTSyncTest.java:22)
    at EDTSyncTest.access$0(EDTSyncTest.java:17)
    at EDTSyncTest$Delayer.done(EDTSyncTest.java:51)
    at javax.swing.SwingWorker$5.run(Unknown Source)
    /* ... */
    at java.awt.Dialog.setVisible(Unknown Source)
    at EDTSyncTest.test(EDTSyncTest.java:27)
    at EDTSyncTest.startTest(EDTSyncTest.java:35)
    at EDTSyncTest.jButton1ActionPerformed(EDTSyncTest.java:75)
    at EDTSyncTest.access$1(EDTSyncTest.java:74)
    at EDTSyncTest$1.actionPerformed(EDTSyncTest.java:66)
    at javax.swing.AbstractButton.fireActionPerformed(Unknown Source)
    /* ... */
    at java.awt.EventDispatchThread.run(Unknown Source)
1 after dialog, EDT=true
0 after dialog, EDT=true
johnchen902
  • 9,531
  • 1
  • 27
  • 69