I think I figured out what is happening in the example. After pondering the Dialog.show() code (which is not easy to ponder ...) and being tipped off by some OpenJavadoc on the www, I found the following in the package-private java.awt.EventDispatchThread class:
"The Thread starts a "permanent" event pump with a call to
pumpEvents(Conditional) in its run() method. Event handlers can choose
to block this event pump at any time, but should start a new pump (not
a new EventDispatchThread) by again calling pumpEvents(Conditional).
..."
So, as intimated by the answer in the post referenced above, the "All dialogs are modal ..." statement is true - in a sense, but when the EDT is the executor that thread doesn't really "block". Each JOptionPane is wrapped in a JDialog, whose ctor is an "Event handler" in the vernacular of the EventDispatchThread doc. The dialog is then realized via JDialog.show(). JDialog.show() doesn't return until the dialog is dismissed, but it doesn't block the EDT either - instead it does some magic with the focus system and event filters to produce the modal effect - AND immediately launches a new event pump (executing on the same EDT) as "Event handlers" are instructed to do in the EventDispatchThread doc. This is all unwound when the user dismisses the JOP.
In my example the caller is not the EDT, so invokeLater() dutifully posts all the JOP ctors on the system event queue, as I desire. The running event pump processes the first showMessageDialog() and does not return, but does start a new event pump on the EDT, which immediately processes the 2nd showMessageDialog(), turning it into a JDialog which does not return but does start a new event pump on the EDT, ad infinitum. Each time, JDialog works its focus system magic, meaning that the last dialog displayed is the only one that is accessible in the GUI - this focus trick makes the Swing "appear" to be waiting on the last JOP - when in reality the EDT continues happily along. It has to be something like this, else the GUI input to dismiss the JOP would never be processed and it really would hang.
This behavior explains why (for instance) you find the following in the Javadoc for javax.swing.SwingWorker.get():
"When you want the SwingWorker to block on the Event Dispatch Thread
we recommend that you use a modal dialog."
=========================
To obtain the desired FIFO behavior described in the OP while the non-EDT continues to work, I settled on the following. Constructive comments in re reliability or pattern are welcome.
Some things to note:
- For brevity I started the daemon thread in the ctor, which is not advisable. If using this, you should modify it as described in this comment: https://stackoverflow.com/a/28929746/3006394.
- A fixed capacity queue is used. A LinkedBlockingQueue can be used if unbounded capacity (up to Integer.MAX_VALUE) is required.
- If your needs extend to things such as the ability to shutdown the Announcer, monitor its progress, retrieve user input from each JOptionPane interaction, and so on, the java.util.concurrent.Executors class may provide a better framework than the simple Thread I use here.
import javax.swing.*;
public class TripleVision {
public static void main( String args[] ) throws InterruptedException {
Announcer myAnnouncer = new Announcer();
myAnnouncer.announce( "Enqueued first" );
myAnnouncer.announce( "Enqueued second" );
myAnnouncer.announce( "Enqueued third" );
System.out.println( "Continuing on, doing other things now" );
Thread.sleep( 7000 ); // Just a kludge to prevent this example
// from terminating before the first JOP
} // main() // is realized
private static class Announcer {
/* Operations of the BlockingQueue are atomic, so we do not have to
* synchronize our accesses. */
private final java.util.concurrent.BlockingQueue
requests = new java.util.concurrent.ArrayBlockingQueue ( 15, false );
private Runnable annTask = new Runnable() {
public void run() {
try {
while ( true ) {
final Object msg = requests.take();
SwingUtilities.invokeAndWait( new Runnable() {
public void run() {
JOptionPane.showMessageDialog( null, msg );
} // run()
} // annonymous Runnable
); // invokeAndWait()
} // while
} // try
catch ( InterruptedException ex ) {} // Not used
catch ( InvocationTargetException ex ) {} // Bad karma in run()
ex.printStackTrace();
} // catch
} // run()
}; // annTask Runnable
public Announcer() {
Thread thd = new Thread( annTask );
thd.setName( "Announcer-" + thd.getName() );
thd.setDaemon( true ); // JVM will terminate it w/o complaint
thd.start();
} // Announcer()
public void announce( Object announcement ) {
requests.offer( announcement );
} // announce()
} // Announcer
} // TripleVision